¿Por qué los configuradores de Ruby necesitan "yo"? calificación dentro de la clase?
Los definidores de Ruby, ya sean creados manualmente (c)attr_accessor
o manualmente, parecen ser los únicos métodos que necesitan self.
calificación cuando se accede a ellos dentro de la propia clase. Esto parece dejar a Ruby sola en el mundo de los idiomas:
- Todos los métodos necesitan
self
/this
(como Perl y creo que Javascript) - No se requieren métodos
self
/this
es (C#, Java) - Sólo los armadores necesitan
self
/this
(¿Ruby?)
La mejor comparación es C# vs Ruby, porque ambos lenguajes admiten métodos de acceso que funcionan sintácticamente como variables de instancia de clase foo.x = y
:y = foo.x
. C# los llama propiedades.
He aquí un ejemplo sencillo; el mismo programa en Ruby y luego en C#:
class A
def qwerty; @q; end # manual getter
def qwerty=(value); @q = value; end # manual setter, but attr_accessor is same
def asdf; self.qwerty = 4; end # "self." is necessary in ruby?
def xxx; asdf; end # we can invoke nonsetters w/o "self."
def dump; puts "qwerty = #{qwerty}"; end
end
a = A.new
a.xxx
a.dump
quítale self.qwerty =()
y falla (Ruby 1.8.6 en Linux y OS X). Ahora C#:
using System;
public class A {
public A() {}
int q;
public int qwerty {
get { return q; }
set { q = value; }
}
public void asdf() { qwerty = 4; } // C# setters work w/o "this."
public void xxx() { asdf(); } // are just like other methods
public void dump() { Console.WriteLine("qwerty = {0}", qwerty); }
}
public class Test {
public static void Main() {
A a = new A();
a.xxx();
a.dump();
}
}
Pregunta: ¿Es esto cierto? ¿Hay otras ocasiones además de los emisores en las que uno mismo es necesario? Es decir, ¿hay otras ocasiones en las que no se puede invocar un método Ruby sin uno mismo?
Ciertamente, hay muchos casos en los que el yo se vuelve necesario. Esto no es exclusivo de Ruby, para que quede claro:
using System;
public class A {
public A() {}
public int test { get { return 4; }}
public int useVariable() {
int test = 5;
return test;
}
public int useMethod() {
int test = 5;
return this.test;
}
}
public class Test {
public static void Main() {
A a = new A();
Console.WriteLine("{0}", a.useVariable()); // prints 5
Console.WriteLine("{0}", a.useMethod()); // prints 4
}
}
La misma ambigüedad se resuelve de la misma manera. Pero aunque sutil, pregunto sobre el caso en el que
- Se ha definido un método y
- No se ha definido ninguna variable local y
Nos encontramos
qwerty = 4
lo cual es ambiguo: ¿se trata de una invocación de método o de una nueva asignación de variable local?
@Mike Piedra
¡Hola! Entiendo y aprecio los puntos que ha planteado y su ejemplo fue excelente. Créame cuando digo que si tuviera suficiente reputación, votaría a favor de su respuesta. Sin embargo, todavía no estamos de acuerdo:
- por una cuestión de semántica y
- sobre un punto central de hecho
En primer lugar, afirmo, no sin ironía, que estamos teniendo un debate semántico sobre el significado de "ambigüedad".
Cuando se trata de analizar y programar la semántica del lenguaje (el tema de esta pregunta), seguramente admitiría un amplio espectro de la noción de "ambigüedad". Adoptemos alguna notación aleatoria:
- ambiguo: ambigüedad léxica (lex debe 'mirar hacia adelante')
- Ambiguo: ambigüedad gramatical (yacc debe ceder al análisis del árbol de análisis)
- AMBIGUO: ambigüedad sabiendo todo en el momento de la ejecución.
(y también hay basura entre 2 y 3). Todas estas categorías se resuelven recopilando más información contextual, mirando cada vez más globalmente. Entonces cuando dices,
"qwerty = 4" no es ambiguo en C# cuando no hay ninguna variable definida...
No podría estar mas de acuerdo. Pero de la misma manera, estoy diciendo
"qwerty = 4" no es ambiguo en Ruby (tal como existe ahora)
"qwerty = 4" es ambiguo en C#
Y todavía no nos contradecimos. Finalmente, aquí es donde realmente no estamos de acuerdo: Ruby podría o no implementarse sin más construcciones de lenguaje tales que,
Para "qwerty = 4", Ruby invoca SIN ambigüedades un definidor existente si
no hay una variable local definida
Dices que no. Yo digo si; Podría existir otro rubí que se comporte exactamente como la corriente en todos los aspectos, excepto que "qwerty = 4" define una nueva variable cuando no existe ningún setter ni local, invoca al setter si existe y asigna al local si existe. Acepto plenamente que podría estar equivocado. De hecho, sería interesante encontrar una razón por la que podría estar equivocado.
Dejame explicar.
Imagine que está escribiendo un nuevo lenguaje OO con métodos de acceso que parecen instancias vars (como Ruby y C#). Probablemente comenzarías con gramáticas conceptuales como:
var = expr // assignment
method = expr // setter method invocation
Pero el analizador-compilador (ni siquiera el tiempo de ejecución) vomitará, porque incluso después de asimilar toda la entrada no hay manera de saber qué gramática es pertinente. Te enfrentas a una elección clásica. No puedo estar seguro de los detalles, pero básicamente Ruby hace esto:
var = expr // assignment (new or existing)
// method = expr, disallow setter method invocation without .
es por eso que no es ambiguo, mientras que C# hace esto:
symbol = expr // push 'symbol=' onto parse tree and decide later
// if local variable is def'd somewhere in scope: assignment
// else if a setter is def'd in scope: invocation
Para C#, "más tarde" todavía está en tiempo de compilación.
Estoy seguro de que Ruby podría hacer lo mismo, pero 'más tarde' tendría que ser en tiempo de ejecución, porque, como señala Ben, no se sabe hasta que se ejecuta la declaración qué caso se aplica.
Mi pregunta nunca tuvo la intención de significar "¿realmente necesito el 'yo'?" o "¿qué posible ambigüedad se está evitando?" Más bien quería saber por qué se hizo esta elección en particular. Quizás no sea rendimiento. Tal vez simplemente hizo el trabajo, o se consideró mejor permitir siempre que un local de 1 línea anule un método (un requisito de caso bastante raro)...
Pero en cierto modo estoy sugiriendo que el lenguaje más dinámico podría ser el que posponga esta decisión por más tiempo y elija la semántica basada en la información más contextual: por lo tanto, si no tiene un local y definió un definidor, usaría el definidor. . ¿No es por eso que nos gusta Ruby, Smalltalk, Objc, porque la invocación del método se decide en tiempo de ejecución, ofreciendo la máxima expresividad?
Bueno, creo que la razón por la que este es el caso es porque qwerty = 4
es ambiguo: ¿estás definiendo una nueva variable llamada qwerty
o llamando al definidor? Ruby resuelve esta ambigüedad diciendo que creará una nueva variable, por lo que self.
es obligatoria.
Aquí hay otro caso en el que necesitas self.
:
class A
def test
4
end
def use_variable
test = 5
test
end
def use_method
test = 5
self.test
end
end
a = A.new
a.use_variable # returns 5
a.use_method # returns 4
Como puede ver, el acceso a test
es ambiguo, por lo que self.
es obligatorio.
Además, esta es la razón por la que el ejemplo de C# en realidad no es una buena comparación, porque las variables se definen de una manera que no es ambigua al usar el definidor. Si hubiera definido una variable en C# que tuviera el mismo nombre que el descriptor de acceso, necesitaría calificar las llamadas al descriptor de acceso con this.
, tal como en el caso de Ruby.
Lo importante a recordar aquí es que los métodos de Ruby se pueden (des)definir en cualquier momento, por lo que para resolver inteligentemente la ambigüedad, cada asignación necesitaría ejecutar código para verificar si hay un método con el nombre asignado en ese momento. de asignación.