2009-03-07 17 views
22

Me gustaría hacer un poco de reflexión bastante resistente en Ruby. Quiero crear una función que devuelva los nombres de los argumentos de varias funciones de llamada más arriba en la pila de llamadas (solo una más sería suficiente, pero ¿por qué detenerse ahí?). Podría usar Kernel.caller, ir al archivo y analizar la lista de argumentos, pero sería feo y poco confiable.Cómo obtener nombres de argumento usando la reflexión

La función que me gustaría que funcionaría de la siguiente manera:

module A 
    def method1(tuti, fruity) 
    foo 
    end 
    def method2(bim, bam, boom) 
    foo 
    end 
    def foo 
    print caller_args[1].join(",") #the "1" mean one step up the call stack 
    end 

end 

A.method1 
#prints "tuti,fruity" 
A.method2 
#prints "bim, bam, boom" 

no me importaría usar ParseTree o alguna herramienta similar para esta tarea, pero mirando al árbol de traducción, no resulta evidente cómo usarlo para este propósito. Crear una extensión C como this es otra posibilidad, pero sería bueno si alguien ya lo hubiera hecho por mí.

Veo que probablemente necesite algún tipo de extensión C. Supongo que eso significa que mi pregunta es qué combinación de extensión C funcionaría más fácilmente. No creo que el llamador + ParseTree sea suficiente por sí mismos.

En cuanto a por qué me gustaría hacer esto, en lugar de decir "depuración automática", quizás debería decir que me gustaría utilizar esta funcionalidad para hacer una comprobación automática de las condiciones de llamada y devolución de funciones:

def add x, y 
    check_positive 
    return x + y 
end 

Dónde check_positive lanzaría una excepción si x y y no fueron positivos. Obviamente, habría más que eso, pero espero que esto proporcione suficiente motivación.

+0

Parece que desea determinar el orden de ejecución y los argumentos. ¿Hay alguna razón por la que no puedas usar el registro? Probablemente puedas incluso hacer metaprogramación para abrir todos los métodos en la clase y automáticamente registrar los argumentos para hacer lo que quieras. – runako

Respuesta

15

Le sugiero que eche un vistazo a la biblioteca action-args de Merb.

require 'rubygems' 
require 'merb' 

include GetArgs 

def foo(bar, zed=42) 
end 

method(:foo).get_args # => [[[:bar], [:zed, 42]], [:zed]] 

Si no quiere depender de Merb, puede elegir y escoger las mejores partes de the source code in github.

4

Tengo un método que es bastante caro y casi funciona.

$shadow_stack = [] 

set_trace_func(lambda { 
    |event, file, line, id, binding, classname| 
    if event == "call" 
    $shadow_stack.push(eval("local_variables", binding)) 
    elsif event == "return" 
    $shadow_stack.pop 
    end 
}) 

def method1(tuti, fruity) 
    foo 
end 
def method2(bim, bam, boom) 
    foo 
    x = 10 
    y = 3 
end 

def foo 
    puts $shadow_stack[-2].join(", ") 
end 

method1(1,2) 
method2(3,4,4) 

salidas

tuti, fruity 
bim, bam, boom, x, y 

Soy curioso en cuanto a por qué te gustaría dicha funcionalidad de tal manera generalizada.

Tengo curiosidad de cómo crees que esta funcionalidad permitiría la depuración automática? Todavía necesitarías inyectar llamadas a tu función "foo". De hecho, algo basado en set_trace_func es más capaz de ser automático, ya que no necesita tocar el código existente. De hecho, así es como se implementa debug.rb, en términos de set_trace_func.

Las soluciones a su pregunta precisa son básicamente, tal como lo describió. use caller + parsetree, o abra el archivo y tome los datos de esa manera. No hay capacidad de reflexión de la que soy consciente que te permita obtener los nombres de los argumentos. Puede aprobar mi solución agarrando el objeto de método asociado y llamando al #arity para luego inferir qué de local_variables son argumentos, pero aunque parece que el resultado de esa función está ordenado, no estoy seguro de que sea seguro confiar en eso. Si no le importa que pregunte, una vez que tenga los datos y la interfaz que describe, ¿qué va a hacer con eso? La depuración automática no era lo que inicialmente me vino a la mente cuando imaginé usos para esta funcionalidad, aunque tal vez esté fallando la imaginación de mi parte.


Aha!

Me acercaría a esto de manera diferente a continuación. Ya hay varias bibliotecas de ruby ​​para hacer diseño por contrato, incluyendo ruby-contract, rdbc, etc.

Otra opción es escribir algo como:

def positive 
    lambda { |x| x >= 0 } 
end 

def any 
    lambda { |x| true } 
end 

class Module 
    def define_checked_method(name, *checkers, &body) 
    define_method(name) do |*args| 
     unless checkers.zip(args).all? { |check, arg| check[arg] } 
     raise "bad argument" 
     end 
     body.call(*args) 
    end 
    end 
end 

class A 
    define_checked_method(:add, positive, any) do |x, y| 
    x + y 
    end 
end 

a = A.new 
p a.add(3, 2) 
p a.add(3, -1) 
p a.add(-4, 2) 

salidas

5 
2 
checked_rb.rb:13:in `add': bad argument (RuntimeError) 
     from checked_rb.rb:29 

Por supuesto, esto se puede hacer mucho más sofisticado, y de hecho eso es algo de lo que las bibliotecas que he mencionado proporcionan, pero quizás esta es una forma de llevarlo a donde quiere ir sin tomar necesariamente el camino que planeaba usar para llegar allí.

+0

De hecho, el método que describes claramente no distingue los argumentos de las variables locales y tampoco funciona automáticamente –

+0

Bueno, eso funciona para hacer un diseño por contrato, pero parece más que un poco complicado en comparación con tener una sola función que puedas llamar . Lo ideal es poder * fácilmente * agregar y eliminar los controles. –

-3

De hecho, el método que usted describe claramente no puede distinguir los argumentos de las variables locales sin que consiguieran trabajar automáticamente

Esto se debe a lo que estamos tratando de hacer no es algo que se apoya. Es posible (todo es posible en rubí), pero no hay forma documentada o conocida de hacerlo.

Cualquiera puede evaluar la traza inversa como lo que sugiere logan, o puede reventar su compilador de C y piratear el código fuente de ruby. Estoy razonablemente seguro de que no hay otras formas de hacerlo.

39

en Ruby 1.9.2, puede trivialmente obtener la lista de parámetros de cualquier Proc (y por lo tanto, por supuesto, también de cualquier Method o UnboundMethod) con Proc#parameters:

A.instance_method(:method1).parameters # => [[:req, :tuti], [:req, :fruity]] 

El formato es un conjunto de pares de símbolos: tipo (obligatorio, opcional, descanso, bloque) y nombre.

Para el formato que desea, pruebe

A.instance_method(:method1).parameters.map(&:last).map(&:to_s) 
    # => ['tuti', 'fruity'] 

Por supuesto, que todavía no le dan acceso a la persona que llama, sin embargo.

+0

Esto tampoco devuelve valores predeterminados. Por ejemplo: 'def persona (nombre:" calvin ")'. 'method.parameters' dará' [[: key,: name]] '. –

+2

Los valores predeterminados se evalúan dinámicamente, no se puede obtener información estática sobre ellos. ¿Qué debería 'def foo (par = rand()); método (: foo) .parameters' return? –

+0

¿qué significa 'instance_method' aquí? – fraxture

1

si desea que el valor de los valores por defecto, también, están los "argumentos" joya

$ gem install rdp-arguments 
$ irb 
>> require 'arguments' 
>> require 'test.rb' # class A is defined here 
>> Arguments.names(A, :go) 
Cuestiones relacionadas