2012-06-01 13 views
9

¿Hay algún plan para implementar el comportamiento de ruby ​​similar a la función CoffeeScript de especificar un nombre de variable de instancia en una lista de argumentos de método? Al igual que Ruby: establece automáticamente la variable de instancia como argumento de método?

class User 
    def initialize(@name, age) 
    # @name is set implicitly, but @age isn't. 
    # the local variable "age" will be set, just like it currently works. 
    end 
end 

Soy consciente de esta pregunta: in Ruby can I automatically populate instance variables somehow in the initialize method?, pero no parecen todas las soluciones (incluyendo la mía) para adaptarse a la filosofía de rubí simplicidad.

Y, ¿habría algún inconveniente por tener este comportamiento?

ACTUALIZACIÓN

Una de las razones de esto es la SECO (no repita usted mismo) filosofía de la comunidad rubí. A menudo me resulta necesario repetir el nombre de una variable de argumento porque quiero que se asigne a la variable de instancia del mismo nombre.

def initialize(name) 
    # not DRY 
    @name = name 
end 

Una desventaja que se me ocurre es que puede parecer que un método no está haciendo nada si no tiene cuerpo. Si está escaneando rápidamente, esto puede parecer una operación no operativa. Pero creo que dado el tiempo, podemos adaptarnos.

Otro inconveniente: si está configurando otras variables de instancia en el cuerpo, y trata de ser legible poniendo todas las asignaciones al principio, puede tomar más "poder" cognitivo para ver que allí las asignaciones también suceden en la lista de argumentos Pero no creo que esto sea más difícil que, por ejemplo, ver una llamada constante o de método y tener que saltar a su definición.

# notice: instance var assignments are happening in 2 places! 
def initialize(@name) 
    @errors = [] 
end 

Respuesta

4

Después de una cierta ponderación, me preguntaba si es posible conseguir realmente los nombres de los argumentos de un método de rubí. Si es así, podría usar un prefijo de argumento especial como "iv_" para indicar qué argumentos se deben establecer como variables de instancia.

Y es posible: How to get argument names using reflection.

Sí! Entonces puedo escribir un módulo para manejar esto por mí. Luego me quedé atascado porque si llamo al método de ayuda del módulo, no conoce los valores de los argumentos porque son locales para la persona que llama. Ah, pero ruby ​​tiene objetos vinculantes.

Aquí está el módulo (rubí 1.9 solamente):

module InstanceVarsFromArgsSlurper 
    # arg_prefix must be a valid local variable name, and I strongly suggest 
    # ending it with an underscore for readability of the slurped args. 
    def self.enable_for(mod, arg_prefix) 
    raise ArgumentError, "invalid prefix name" if arg_prefix =~ /[^a-z0-9_]/i 
    mod.send(:include, self) 
    mod.instance_variable_set(:@instance_vars_from_args_slurper_prefix, arg_prefix.to_s) 
    end 

    def slurp_args(binding) 
    defined_prefix = self.class.instance_variable_get(:@instance_vars_from_args_slurper_prefix) 
    method_name = caller[0][/`.*?'/][1..-2] 
    param_names = method(method_name).parameters.map{|p| p.last.to_s } 
    param_names.each do |pname| 
     # starts with and longer than prefix 
     if pname.start_with?(defined_prefix) and (pname <=> defined_prefix) == 1 
     ivar_name = pname[defined_prefix.size .. -1] 
     eval "@#{ivar_name} = #{pname}", binding 
     end 
    end 
    nil 
    end 
end 

Y aquí está el uso:

class User 
    InstanceVarsFromArgsSlurper.enable_for(self, 'iv_') 

    def initialize(iv_name, age) 
    slurp_args(binding) # this line does all the heavy lifting 
    p [:iv_name, iv_name] 
    p [:age, age] 
    p [:@name, @name] 
    p [:@age, @age] 
    end 
end 

user = User.new("Methuselah", 969) 
p user 

Salida:

[:iv_name, "Methuselah"] 
[:age, 969] 
[:@name, "Methuselah"] 
[:@age, nil] 
#<User:0x00000101089448 @name="Methuselah"> 

No le permiten tener una lata vacía cuerpo del método, pero está SECO. Estoy seguro de que se puede mejorar aún más simplemente especificando qué métodos deberían tener este comportamiento (implementado a través de alias_method), en lugar de llamar a slurp_args en cada método; la especificación debería ser después de todos los métodos definidos.

Tenga en cuenta que el nombre del método de módulo y ayuda probablemente podría mejorarse. Acabo de usar lo primero que se me viene a la mente.

+0

Gracias por esta respuesta. Sin embargo, definitivamente parece más trabajo que valga la pena. :) – hrdwdmrbl

+0

@jackquack Bueno, realmente solo tienes que hacer la configuración una vez. Luego en tus clases, todo lo que tienes que hacer es llamar 'enable_for' y' slurp_args'. – Kelvin

0

Creo que responde a su propia pregunta, no se ajusta a la filosofía de rubí simplicidad. Agregaría una complejidad adicional a la forma en que se manejan los parámetros en los métodos y moverá la lógica para administrar las variables hasta los parámetros del método. Puedo ver el argumento de que hace que el código sea menos legible que un lanzamiento, pero me parece poco prolijo.

Algunos escenarios del @param tendría que lidiar con:

def initialize(first, last, @scope, @opts = {}) 

def search(@query, condition) 

def ratchet(@*arg ) 

caso de todos estos escenarios sea válida? ¿Solo el initialize? El @*arg parece particularmente incierto en mi mente. Todas estas reglas y exclusiones hacen que el lenguaje Ruby sea más complicado. Para el beneficio de las variables de instancia automática, no creo que valga la pena.

+2

Me imagino que una instancia salpicada arg es '* @ arg'. Aparte de eso, ninguno de los escenarios parece particularmente feo, complicado o incluso difícil de seguir. En términos de la lógica var de instancia, ya puede establecerlos en la lista arg, aunque de forma artificial: 'def run (a = (@ v = 1)); fin'. Sí, es feo y la var solo se establece si no se pasa arg. Pero demuestra que este tipo de asignación ya está permitido. Creo que ayuda a mantener el código DRYer. Voy a agregar otro ejemplo a mi pregunta. – Kelvin

+0

Vaya, cuando dije que hace código DRYer, quise decir la propuesta en mi pregunta, no el ejemplo artificial en mi comentario. – Kelvin

2

Bueno, en realidad ...

class User 
    define_method(:initialize) { |@name| } 
end 

User.new(:name).instance_variable_get :@name 
# => :name 

Obras en 1.8.7, pero no en 1.9.3. Ahora, justo donde Qué aprendí acerca de esto ...

+2

+1 para un valiente intento. Todavía no lo he probado pero es más una novedad si no funciona en 1.9.3. – Kelvin

Cuestiones relacionadas