2010-06-22 7 views
17

Soy bastante nuevo para Ruby, y hasta ahora, descubrir cómo usar "binding" objects es uno de los mayores puntos débiles para mí. Si estoy leyendo la documentación correctamente, son casi completamente opacos. Para acceder al alcance dentro del objeto vinculante, debe tener una cadena de código Ruby y eval usando el enlace.¿Es 'eval' la única forma de interactuar con objetos vinculantes en Ruby?

Tal vez soy solo un purista de una escuela diferente, pero soy alérgico a las construcciones 'eval' basadas en cuerdas, en general. ¿Hay alguna manera de hacer cualquiera de los siguientes, de forma segura y en el caso general, dado un objeto de enlace:

  1. Lista de los identificadores de alcance en el contexto representa la unión, o recuperar un hash de los contenidos.
  2. Establezca el valor de una variable local en el enlace igual a la de una variable local en un contexto externo. Idealmente, esto debería funcionar en general, incluso si el valor es una referencia de objeto, manejador de archivo o alguna otra entidad compleja.
  3. (extensión 2 :) Dado un hash, configure los locales en el enlace de cada entrada.
  4. Mejor aún, dado un hash construir un enlace con solo las construcciones de lenguaje básico y los nombres en el hash en el alcance.

Básicamente, quiero saber cuál de esos es posible y cómo lograr los que sí lo son. Me imagino que las soluciones para cada uno estarán bastante relacionadas, por eso planteo todo esto en una sola pregunta.

Como alternativa, ¿hay alguna manera de evaluar el código que ya se ha analizado en el contexto de un enlace, similar a la sintaxis de eval BLOCK de Perl?

+1

esta pregunta necesita más @ Jörg W Mittag: P –

Respuesta

7

en buscar más, encontré una respuesta a al menos una parte de mi pregunta:

Basado en: http://wikis.onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc/style/print

El resto es de la experimentación después de votos punteros de Jim Shubert.

  1. Esto se puede lograr por eval -ing local_variables, instance_variables y global_variables dentro de la unión.
  2. Puede hacer algo como se describe a continuación, dado var_name, new_val, my_binding (la sintaxis puede ser imperfecta o mejorable, no dude en sugerir en los comentarios. Además, no pude obtener el formato del código para trabajar dentro de la lista, sugerencias para cómo hacerlo también se implementará.)
  3. Puede tomar (2) y repetir el hash para hacer esto.
  4. Vea el segundo bloque de código a continuación. La idea es comenzar con TOPLEVEL_BINDING, que normalmente solo incluye las variables globales.

Esto implica el uso de la cadena eval. Sin embargo, ningún valor de variable se expande nunca en las cadenas involucradas, por lo que debe ser bastante seguro si se usa como se describe, y debe trabajar para 'pasar' valores complejos de variables.

También tenga en cuenta que siempre es posible hacer eval var_name, my_binding para obtener el valor de una variable.Tenga en cuenta que en todos estos usos es vital que el nombre de la variable sea seguro de evaluar, por lo que idealmente no debería provenir de ningún tipo de entrada del usuario.

Establecimiento de una variable dentro de una unión dada var_name, new_val, my_binding:

# the assignment to nil in the eval coerces the variable into existence at the outer scope 
setter = eval "#{var_name} = nil; lambda { |v| #{var_name} = v }", my_binding 
setter.call(new_val) 

La construcción de una "medida" Encuadernación:

my_binding = eval "lambda { binding }", TOPLEVEL_BINDING # build a nearly-empty binding 
# set_in_binding is based on the above snippet 
vars_to_include.each { |var_name, new_val| set_in_binding(var_name, new_val, my_binding) } 
+1

¡Excelente investigación! –

+3

Aceptando esto, pero si viene alguien que sepa cómo evitar el uso de eval de cadena para esto, por favor contribuya con su conocimiento. –

3

Walter, usted debería ser capaz de interactuar directamente con la unión . No he trabajado mucho con fijaciones antes, pero me encontré un par de cosas en el IRB:

[email protected]:~> irb 
irb(main):001:0> eval "self", TOPLEVEL_BINDING 
=> main 
irb(main):002:0> eval "instance_variables", TOPLEVEL_BINDING 
=> [] 
irb(main):003:0> eval "methods", TOPLEVEL_BINDING 
=> ["irb_kill", "inspect", "chws", "install_alias_method", .... 

también tengo Metaprogramación Rubí, que no habla mucho acerca del enlace. Sin embargo, si usted escoge esto, al final de la página 144 se dice

En cierto sentido, se puede ver Encuadernación objetos como una forma "pura" de los cierres que bloquea, porque estos objetos contienen una alcance pero no contiene código.

Y, en la página opuesta, sugiere jugando con el código del IRB (quitando los dos últimos argumentos de la llamada eval) para ver cómo se utiliza fijaciones:

// CTWC/CRI/workspace.rb
eval (declaraciones, @binding) #, archivo, línea)

... Y yo iba a sugerir que pasa la lambda, pero veo que acaba de responder eso, así que dejaré el retoques IRB como una sugerencia para futuras investigaciones.

+0

Gracias, el constructo 'instance_variables' es nuevo para mí, eso me da más puntos de partida. Las instrucciones para verificar irb también son útiles. He votado a favor pero no lo acepté, ya que no responde las preguntas directamente. –

+0

Walter, gracias. Espero que al menos te haya dado algunas ideas para trabajar y encontrar la respuesta. Estoy seguro de que lo que has preguntado es posible, teniendo en cuenta que irb funciona directamente con enlaces (irb es una clase de ruby ​​en sí misma). –

1

¿Podría explicarme qué está tratando de hacer exactamente? Proporcione algún código que muestre cómo desea que funcione. Puede haber una forma mejor y más segura de lograr lo que desea.

Voy a intentar adivinar el caso de uso típico. dado un Hash: { : a => 11,: b => 22}

desea anular relativamente un entorno mínimo, la ejecución donde se puede acceder a los valores del hash como variables locales. (No estoy seguro de por qué necesitarías que fueran locales, excepto tal vez si estás escribiendo un DSL, o si ya tienes un código escrito que no quieres adaptar para acceder al hash)

Aquí está mi intento. Para simplificar, asumo que solo usas símbolos como teclas Hash.

 
class MyBinding 
    def initialize(varhash); @vars=varhash; end 
    def method_missing(methname, *args) 
    meth_s = methname.to_s 
    if meth_s =~ /=\z/ 
     @vars[meth_s.sub(/=\z/, '').to_sym] = args.first 
    else 
     @vars[methname] 
    end 
    end 
    def eval(&block) 
    instance_eval &block 
    end 
end 

Ejemplo de uso:

 
hash = {:a => 11, :b => 22} 
mb = MyBinding.new hash 
puts mb.eval { a + b } 
# setting values is not as natural: 
mb.eval { self.a = 33 } 
puts mb.eval { a + b } 

Algunas advertencias:

1) No crié un NameError si no existiera la variable, sino una simple sustitución correcciones que:

 
def initialize(varhash) 
    @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" } 
    @vars.update(varhash) 
end 

2) Las reglas normales de alcance son tales que si existe un local cuyo nombre es igual a un método, el local toma p recedence, a menos que realice explícitamente una llamada a método, como a().La clase anterior tiene el comportamiento opuesto; el método tiene prioridad. Para obtener un comportamiento "normal", deberá ocultar todos (o la mayoría) de los métodos estándar, como #object_id. ActiveSupport proporciona una clase BlankSlate para este propósito; solo mantiene 3 métodos:

 __send__, instance_eval, __id__

. Para usarlo, simplemente haga que MyBinding herede de BlankSlate. Además de estos 3 métodos, tampoco podrás tener locales llamados "eval" o "method_missing".

3) Establecer un local no es tan natural, porque necesita "auto" para recibir la llamada al método. No estoy seguro si hay una solución para esto.

4) El bloque eval puede interferir con el hash @vars.

5) Si tiene una var local real en el alcance donde llama a mb.eval, con el mismo nombre que una de las teclas hash, el local real tendrá prioridad ... esto es probablemente el mayor inconveniente porque errores sutiles pueden arrastrarse.

Después de darme cuenta de las desventajas de mi técnica, recomiendo usar un Hash directamente para mantener un conjunto de variables, a menos que vea una razón diferente. Pero si aún desea usar la evaluación nativa, puede mejorar la seguridad utilizando Regexps para evitar la inyección de código, y "localmente" estableciendo $ SAFE para ser mayor para la llamada eval utilizando un Proc, como sigue:

 
proc { $SAFE = 1; eval "do_some_stuff" }.call # returns the value of eval call 
0

Aquí hay algunos códigos para una solución basada en Hash.

 
class ScopedHash 
    def initialize(varhash) 
    # You can use an OpenStruct instead of a Hash, but the you lose the NameError feature. 
    # OpenStructs also don't have the ability to list their members unless you call a protected method 
    @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" } 
    @vars.update(varhash) 
    end 
    def eval 
    yield @vars 
    end 
end 

if __FILE__ == $0 
    # sample usage 
    hash = {:a => 11, :b => 22} 
    sh = ScopedHash.new hash 
    puts sh.eval {|v| v[:a] + v[:b] } 
    sh.eval {|v| v[:a] = 33 } 
    puts sh.eval {|v| v[:a] + v[:b] } 
    sh.eval{|v| v[:c] } # raises NameError 
end 

Así, en lugar de utilizar los locales, usted acaba de acceder a la Hash cedido. Creo que hay muy pocas razones por las cuales uno se vería obligado a manipular un objeto Vinculante; generalmente hay formas más limpias de realizar la tarea.

+0

Independientemente de la conveniencia de manipular enlaces, ese fue el tema de esta pregunta. Esta sugerencia no responde la pregunta. -1 –

+0

De acuerdo, está fuera de tema. Estoy tratando de ser útil, y aún quiero estarlo, ¿puedes proporcionar un caso de uso? ¿O tal vez su objetivo es simplemente experimentar específicamente con Binding como un constructo de lenguaje y no se preocupe por nada que no use Bindings? Si es así, simplemente dígalo y trataré de pensar en una mejor solución con enlaces. – Kelvin

+0

La pregunta es más de esto último. Creo que se me ocurrió cuando experimentaba con plantillas, pero mi verdadera razón para preguntarlo era para saber qué tan bien maneja el lenguaje los objetos vinculantes de introspección. Sí, eso fue todo: quería crear un espacio de nombres controlado para la invocación de plantillas con solo los valores de un conjunto de hash en particular, sin tener que cambiar la forma en que se escribió la plantilla para que pasara por un hash. –

Cuestiones relacionadas