¿Por qué un objeto Regexp se considera "falso" en Ruby?

16

Ruby tiene una idea universal de " veracidad " y " falsedad ".

Rubí hace tener dos clases específicas de objetos de tipo booleano, TrueClassy FalseClass, con instancias singleton denotados por las variables especiales truey false, respectivamente.

Sin embargo, la veracidad y la falsedad no se limitan a las instancias de esas dos clases, el concepto es universal y se aplica a todos los objetos en Ruby. Todo objeto es verdadero o falso . Las reglas son muy simples. En particular, solo dos objetos son falsos :

Todo otro objeto es verdadero . Esto incluye incluso objetos que se consideran falsos en otros lenguajes de programación, como

Estas reglas están integradas en el lenguaje y no son definibles por el usuario. No hay to_boolconversión implícita ni nada similar.

Aquí hay una cita de la especificación ISO Ruby Language :

6.6 Valores booleanos

Un objeto se clasifica en un objeto verdadero o un objeto falso .

Solo falso y nulo son objetos falsos. falso es la única instancia de la clase FalseClass(ver 15.2.6), a la que se evalúa una expresión falsa (ver 11.5.4.8.3). nil es la única instancia de la clase NilClass(ver 15.2.4), a la que se evalúa una expresión nil (ver 11.5.4.8.2).

Los objetos distintos de falso y nulo se clasifican en objetos verdaderos. verdadero es la única instancia de la clase TrueClass(ver 15.2.5), a la que se evalúa una expresión verdadera (ver 11.5.4.8.3).

El ejecutable Ruby / Spec parece estar de acuerdo :

it "considers a non-nil and non-boolean object in expression result as true" do
  if mock('x')
    123
  else
    456
  end.should == 123
end

Según esas dos fuentes, supongo que los Regexps también son verdaderos , pero según mis pruebas, no lo son:

if // then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are falsy'

Probé esto en YARV 2.7.0-preview1 , TruffleRuby 19.2.0.1 y JRuby 9.2.8.0 . Las tres implementaciones están de acuerdo entre sí y no están de acuerdo con la especificación ISO Ruby Language y mi interpretación de Ruby / Spec.

Más precisamente, los Regexpobjetos que son el resultado de evaluar Regexp literales son falsos , mientras Regexpque los objetos que son el resultado de alguna otra expresión son verdaderos :

r = //
if r then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are truthy'

¿Es esto un error o un comportamiento deseado?

Jörg W Mittag
fuente
Lo interesante es que Regex.new("a")es verdad.
mrzasa
!!//es falso pero !!/r/es cierto Extraño de hecho.
máximo
@max !!/r/produce falsepara mí usando (RVM) Ruby 2.4.1.
3limin4t0r
Lo siento, mi mal @ 3limin4t0r. Tienes razón. Debo haber hecho algo realmente estúpido como dejar un signo de exclamación.
max
2
Una hipótesis, creo que //en el if // thense interpreta como una prueba (un acceso directo para if //=~nil then) (que es siempre Falsy cualquiera que sea el patrón) y no como una instancia Regexp.
Casimir et Hippolyte

Respuestas:

6

Esto no es un error. Lo que está sucediendo es que Ruby está reescribiendo el código para que

if /foo/
  whatever
end

efectivamente se convierte

if /foo/ =~ $_
  whatever
end

Si está ejecutando este código en un script normal (y no está utilizando la -eopción), debería ver una advertencia:

warning: regex literal in condition

Probablemente esto sea algo confuso la mayor parte del tiempo, por eso se da la advertencia, pero puede ser útil para una línea utilizando la -eopción Por ejemplo, puede imprimir todas las líneas que coinciden con una expresión regular dada de un archivo con

$ ruby -ne 'print if /foo/' filename

(El argumento predeterminado para printes $_también.)

mate
fuente
Véase también -n, -p, -ay -lopciones, así como el puñado de métodos Kernel que sólo están disponibles cuando -no -pse utilizan ( chomp, chop, gsuby sub).
mate
También hay una segunda parte del analizador donde se emite esa advertencia. Sin embargo, no sé qué está pasando allí.
mate
Creo que la "segunda parte" es la que realmente se aplica a esta pregunta. NODE_LITcon el tipo T_REGEXP. El que publicó en su respuesta es para un literal dinámicoRegexp , es decir, un Regexpliteral que utiliza interpolación, por ejemplo /#{''}/.
Jörg W Mittag
@ JörgWMittag Creo que tienes razón. Escudriñando en el compilador y el código de bytes generado, parece que en el caso de la expresión dinámica regexp el árbol de análisis se reescribe para agregar explícitamente $_como un nodo que el compilador maneja de forma normal, mientras que en el caso estático todo es manejado por compilador. Lo cual es una pena para mí porque "oye, puedes ver dónde se reescribe el árbol de análisis aquí" es una buena respuesta.
mate
4

Este es el resultado de (por lo que puedo decir) una característica indocumentada del lenguaje ruby, que se explica mejor con esta especificación :

it "matches against $_ (last input) in a conditional if no explicit matchee provided" do
  -> {
    eval <<-EOR
    $_ = nil
    (true if /foo/).should_not == true
    $_ = "foo"
    (true if /foo/).should == true
    EOR
  }.should complain(/regex literal in condition/)
end

En general, se puede considerar $_como la "última cadena leída por gets"

Para hacer las cosas aún más confusas, $_(junto con $-) no es una variable global; Tiene alcance local .


Cuando un script ruby comienza, $_ == nil.

Entonces, el código:

// ? 'Regexps are truthy' : 'Regexps are falsey'

Se interpreta como:

(// =~ nil) ? 'Regexps are truthy' : 'Regexps are falsey'

... Que vuelve falsey.

Por otro lado, para una expresión regular no literal (por ejemplo, r = //or Regexp.new('')), esta interpretación especial no se aplica.

//es veraz al igual que todos los demás objetos en rubí además de nily false.


A menos que ejecute un script ruby ​​directamente en la línea de comando (es decir, con la -ebandera), el analizador ruby ​​mostrará una advertencia contra dicho uso:

advertencia: regex literal en condición

Usted podría hacer uso de este comportamiento en un guión, con algo como:

puts "Do you want to play again?"
gets
# (user enters e.g. 'Yes' or 'No')
/y/i ? play_again : back_to_menu

... Pero sería más normal asignar una variable local al resultado getsy realizar la comprobación de expresiones regulares contra este valor explícitamente.

No conozco ningún caso de uso para realizar esta verificación con una expresión regular vacía , especialmente cuando se define como un valor literal. El resultado que ha resaltado realmente sorprendería a la mayoría de los desarrolladores de ruby ​​con la guardia baja.

Tom Lord
fuente
Solo usé el condicional como ejemplo. !// #=> truetiene el mismo comportamiento y no está en un condicional. No pude encontrar ningún contexto booleano (condicional o no), donde se comporta como se esperaba.
Jörg W Mittag
@ JörgWMittag ¿Quiere decir, por ejemplo, !// ? true : falsedevoluciones true? Creo que este es el mismo punto nuevamente: se está interpretando como:!(// =~ nil) ? true : false
Tom Lord
Si configura manualmente $_ = 'hello world'antes de ejecutar el código anterior, entonces debería obtener un resultado diferente, porque // =~ 'hello world', pero no coincide nil.
Tom Lord
No, quiero decir !// sin el condicional evalúa a true. La especificación que citó es sobre un Regexpliteral en condicional, pero en este ejemplo, no hay condicional, por lo que esta especificación no se aplica.
Jörg W Mittag
2
Ah .. Sí, muy sorprendente. Sin embargo, el comportamiento parece estar vinculado: puts !//; $_ = ''; puts !//- Supongo que porque el analizador lo expande como una macro; no necesariamente tiene que estar dentro de un condicional?
Tom Lord