¿Dónde y cómo se especifica la variable _ (subrayado)?

81

La mayoría son conscientes del _significado especial de 'en IRB como tenedor del último valor de retorno, pero eso no es lo que estoy preguntando aquí.

En cambio, estoy preguntando sobre _cuándo se usa como nombre de variable en código Ruby simple y antiguo. Aquí parece tener un comportamiento especial, similar a una “variable no importa” (à la Prolog ). A continuación, se muestran algunos ejemplos útiles que ilustran su comportamiento único:

lambda { |x, x| 42 }            # SyntaxError: duplicated argument name
lambda { |_, _| 42 }.call(4, 2) # => 42
lambda { |_, _| 42 }.call(_, _) # NameError: undefined local variable or method `_'
lambda { |_| _ + 1 }.call(42)   # => 43
lambda { |_, _| _ }.call(4, 2)  # 1.8.7: => 2
                                # 1.9.3: => 4
_ = 42
_ * 100         # => 4200
_, _ = 4, 2; _  # => 2

Todos estos se ejecutaron en Ruby directamente (con putss agregados), no en IRB, para evitar conflictos con su funcionalidad adicional.

Sin embargo, todo esto es el resultado de mi propia experimentación, ya que no puedo encontrar ninguna documentación sobre este comportamiento en ninguna parte (es cierto que no es lo más fácil de buscar). En última instancia, tengo curiosidad por saber cómo funciona todo esto internamente para poder comprender mejor exactamente de qué se trata _. Así que estoy pidiendo referencias a la documentación y, preferiblemente, el código fuente de Ruby (y quizás RubySpec ) que revele cómo se _comporta en Ruby.

Nota: la mayor parte de esto surgió de esta discusión con @Niklas B.

Andrew Marshall
fuente

Respuestas:

54

Existe un manejo especial en la fuente para suprimir el error "nombre de argumento duplicado". El mensaje de error solo aparece en el shadowing_lvar_geninterior parse.y, la versión 1.9.3 se ve así :

static ID
shadowing_lvar_gen(struct parser_params *parser, ID name)
{
    if (idUScore == name) return name;
    /* ... */

y idUScorese defineid.c así:

REGISTER_SYMID(idUScore, "_");

Verá un manejo especial similar en warn_unused_var:

static void
warn_unused_var(struct parser_params *parser, struct local_vars *local)
{
    /* ... */
    for (i = 0; i < cnt; ++i) {
        if (!v[i] || (u[i] & LVAR_USED)) continue;
        if (idUScore == v[i]) continue;
        rb_compile_warn(ruby_sourcefile, (int)u[i], "assigned but unused variable - %s", rb_id2name(v[i]));
    }
}

Notará que la advertencia se suprime en la segunda línea del forciclo.

El único manejo especial de _eso que pude encontrar en la fuente 1.9.3 está arriba: se suprime el error de nombre duplicado y se suprime la advertencia de variable no utilizada. Aparte de esas dos cosas, _es simplemente una variable antigua como cualquier otra. No conozco ninguna documentación sobre la especialidad (menor) de _.

En Ruby 2.0, la idUScore == v[i]prueba warn_unused_varse reemplaza con una llamada a is_private_local_id:

if (is_private_local_id(v[i])) continue;
rb_warn4S(ruby_sourcefile, (int)u[i], "assigned but unused variable - %s", rb_id2name(v[i]));

y is_private_local_idsuprime las advertencias para las variables que comienzan con _:

if (name == idUScore) return 1;
/* ... */
return RSTRING_PTR(s)[0] == '_';

en lugar de solo a _sí mismo. Entonces 2.0 afloja un poco las cosas.

mu es demasiado corto
fuente
1
Me pregunto si la diferencia en el comportamiento lambda { |_, _| _ }.call(4, 2)entre 1.8 y 1.9 es solo un efecto secundario involuntario, entonces. Como en circunstancias "normales" en las que el nombre de la variable no se puede duplicar, el orden en el que se asignan es intrascendente.
Andrew Marshall
3
@AndrewMarshall: Sí, creo que el problema del "4 contra 2" es sólo un artefacto de cómo 1.8 y 1.9 manejan la pila. La única vez que se notará es |_,_,...|porque se ha suprimido el error duplicado.
mu es demasiado corto
2
@AndrewMarshall: Me pregunto si todos están leyendo los ChangeLogs de los demás a nuestras espaldas.
mu es demasiado corto
2
Buen hallazgo. Supuse que sería algo tan simple como eso, simplemente suprimiendo el error de nombre de parámetro doble. Ruby realmente es un gran desastre: D
Niklas B.
2
@mu: Por supuesto, todavía tengo que ver una implementación realmente limpia de un lenguaje interpretado (Lua se acerca).
Niklas B.
24

_es un identificador válido. Los identificadores no solo pueden contener guiones bajos, también pueden ser un guión bajo.

_ = o = Object.new
_.object_id == o.object_id
# => true

También puede usarlo como nombres de métodos:

def o._; :_ end
o._
# => :_

Por supuesto, no es exactamente un nombre legible, ni pasa ninguna información al lector sobre a qué se refiere la variable o qué hace el método.

IRB, en particular, establece _el valor de la última expresión:

$ irb
> 'asd'
# => "asd"
> _
# => "asd"

Como está en el código fuente , simplemente se establece _en el último valor:

@workspace.evaluate self, "_ = IRB.CurrentContext.last_value"

Hice un poco de exploración del repositorio. Esto es lo que encontré:

En las últimas líneas del archivo id.c, está la llamada:

REGISTER_SYMID(idUScore, "_");

grepLa fuente de idUScoreme dio dos resultados aparentemente relevantes:

shadowing_lvar_genparece ser el mecanismo mediante el cual el parámetro formal de un bloque reemplaza a una variable del mismo nombre que existe en otro ámbito. Es la función que parece generar "nombre de argumento duplicado" SyntaxErrory la advertencia "sombreado de variable local externa".

Después grepde buscar la fuente shadowing_lvar_gen, encontré lo siguiente en el registro de cambios de Ruby 1.9.3 :

Mar 11 de diciembre 01:21:21 2007 Yukihiro Matsumoto

  • parse.y (shadowing_lvar_gen): no hay error duplicado para "_".

Cuál es probablemente el origen de esta línea :

if (idUScore == name) return name;

De esto, deduzco que en una situación como proc { |_, _| :x }.call :a, :b, una _variable simplemente ensombrece a la otra.


Aquí está el compromiso en cuestión . Básicamente introdujo estas dos líneas:

if (!uscore) uscore = rb_intern("_");
if (uscore == name) return;

De una época en la idUScoreque ni siquiera existía, al parecer.

Matheus Moreira
fuente
6
Esto no explica en absoluto por qué lambda { |_, _| 42 }funciona mientras lambda { |x, x| 42 }que no.
Andrew Marshall
@AndrewMarshall, parece que tienes razón. |_, _|funciona, pero |__, __|no. _parece tener un significado especial, veré si puedo extraer alguna información de la fuente Ruby.
Matheus Moreira
1
Por cierto, su actualización, aunque informativa, no es relevante ya que solo se aplica a IRb, lo cual dije específicamente en mi pregunta que no estaba preguntando.
Andrew Marshall
1
+1 para desenterrar la entrada ChangeLog. Todo el mundo debería ser un hacker de C :)
mu es demasiado corto
1
+1 para el código fuente del IRB. IRB.CurrentContext.last_value es muy interesante de conocer, incluso si no es relevante para la pregunta tal como se plantea. Terminé aquí de una búsqueda en Google sobre guiones bajos de IRB.
Josh