¿Cómo puedo hacer referencia a una función en Ruby?
ruby-1.9 first-class-functions (4)
La diferencia (principal) entre las funciones y los métodos copiados de https://stackoverflow.com/a/26620095/226255
Las funciones se definen fuera de las clases, mientras que los métodos se definen dentro y son parte de las clases.
Ruby no tiene funciones y su def foo termina siendo un método para la clase Object .
Si insiste en definir foo como lo está haciendo arriba, puede extraer su "funcionalidad" haciendo esto:
def foo(a,b)
a+b
end
x = method(:foo).to_proc
x.call(1,2)
=> 3
Explicación:
> method(:foo) # this is Object.method(:foo), returns a Method object bound to
# object of type ''Class(Object)''
=> #<Method: Class(Object)#foo>
method(:foo).to_proc
# a Proc that can be called without the original object the method was bound to
=> #<Proc:0x007f97845f35e8 (lambda)>
Nota IMPORTANTE:
to_proc "copia" las variables de instancia asociadas al objeto del método, si las hay. Considera esto:
class Person
def initialize(name)
@name = name
end
def greet
puts "hello #{@name}"
end
end
greet = Person.new(''Abdo'').method(:greet)
# note that Person.method(:greet) returns an UnboundMethod and cannot be called
# unless you bind it to an object
> greet.call
hello Abdo
=> nil
Conceptualmente, si desea una "función" que funcione en un determinado tipo de objetos, debe ser un método y debe organizar su código como tal. Si solo necesita su "función" en un determinado contexto y desea pasarla, use lambdas:
greet = lambda { |person| "hello #{person}" }
yell_at = lambda { |person| "HELLO #{person.upcase}" }
def do_to_person(person, m)
m.call(person)
end
do_to_person(''Abdo'', greet)
En Python, es bastante sencillo hacer referencia a una función:
>>> def foo():
... print "foo called"
... return 1
...
>>> x = foo
>>> foo()
foo called
1
>>> x()
foo called
1
>>> x
<function foo at 0x1004ba5f0>
>>> foo
<function foo at 0x1004ba5f0>
Sin embargo, parece ser diferente en Ruby ya que un foo desnudo en realidad llama a foo:
ruby-1.9.2-p0 > def foo
ruby-1.9.2-p0 ?> print "foo called"
ruby-1.9.2-p0 ?> 1
ruby-1.9.2-p0 ?> end
=> nil
ruby-1.9.2-p0 > x = foo
foo called => 1
ruby-1.9.2-p0 > foo
foo called => 1
ruby-1.9.2-p0 > x
=> 1
¿Cómo asigno la función foo a x y luego la llamo? ¿O hay una manera más idiomática de hacer esto?
Ruby no tiene funciones. Solo tiene métodos (que no son de primera clase) y Proc s que son de primera clase, pero no están asociados con ningún objeto.
Entonces, este es un método:
def foo(bar) puts bar end
foo(''Hello'')
# Hello
Ah, y sí, este es un método real , no una función o procedimiento de nivel superior o algo así. Los métodos definidos en el nivel superior terminan como métodos de instancia privados (!) En la clase Object :
Object.private_instance_methods(false) # => [:foo]
Este es un Proc :
foo = -> bar { puts bar }
foo.(''Hello'')
# Hello
Observe que los Proc s se llaman de manera diferente a los métodos:
foo(''Hello'') # method
foo.(''Hello'') # Proc
La sintaxis de foo.(bar) es solo azúcar sintáctica para foo.call(bar) (que para Proc s y Method s también tiene un alias de foo[bar] ). Implementar un método de call en tu objeto y luego llamarlo con .() Es lo más cercano que obtendrás a las __call__ ables de Python.
Tenga en cuenta que una distinción importante entre Ruby Proc s y Python lambdas es que no hay restricciones: en Python, un lambda solo puede contener una sola declaración, pero Ruby no tiene la distinción entre declaraciones y expresiones ( todo es una expresión), y, por lo tanto, esta limitación simplemente no existe, por lo tanto, en muchos casos en los que necesita pasar una función nombrada como un argumento en Python porque no puede expresar la lógica en una sola declaración, en Ruby simplemente aprobaría un Proc o un bloque en su lugar, para que el problema de la sintaxis fea para los métodos de referencia ni siquiera surja.
Puede envolver un método en un objeto Method (que esencialmente procesa tipos pato) llamando al Object#method Método Object#method en un objeto (que le dará un Method cuyo self está vinculado a ese objeto en particular):
foo_bound = self.method(:foo)
foo_bound.(''Hello'')
# Hello
También puede usar uno de los métodos de la familia Module#instance_method para obtener un UnboundMethod de un módulo (o clase, obviamente, dado que una clase es un módulo), que luego puede UnboundMethod#bind a un objeto y una llamada en particular. (Creo que Python tiene los mismos conceptos, aunque con una implementación diferente: un método no consolidado simplemente toma el argumento propio explícitamente, al igual que la forma en que se declara).
foo_unbound = Object.instance_method(:foo) # this is an UnboundMethod
foo_unbound.(''Hello'')
# NoMethodError: undefined method `call'' for #<UnboundMethod: Object#foo>
foo_rebound = foo_unbound.bind(self) # this is a Method
foo_rebound.(''Hello'')
# Hello
Tenga en cuenta que solo puede vincular un UnboundMethod a un objeto que sea una instancia del módulo desde el cual tomó el método. No puede usar UnboundMethods para "trasplantar" el comportamiento entre módulos no relacionados:
bar = module Foo; def bar; puts ''Bye'' end; self end.instance_method(:bar)
module Foo; def bar; puts ''Hello'' end end
obj = Object.new
bar.bind(obj)
# TypeError: bind argument must be an instance of Foo
obj.extend(Foo)
bar.bind(obj).()
# Bye
obj.bar
# Hello
Sin embargo, UnboundMethod cuenta que tanto el Method como el Method UnboundMethod son envolturas alrededor del método, no el método en sí. Los métodos no son objetos en Ruby. (Al contrario de lo que he escrito en otras respuestas, BTW. Realmente necesito regresar y corregirlas). Puede envolverlos en objetos, pero no son objetos, y puede ver eso porque esencialmente obtiene lo mismo. Problemas que siempre se obtienen con los envoltorios: identidad y estado. Si llama al method varias veces para el mismo método, obtendrá un objeto de Method diferente cada vez. Si intenta almacenar algún estado en ese objeto de Method (como las cadenas __doc__ estilo __doc__ , por ejemplo), ese estado será privado para esa instancia en particular , y si intenta recuperar su cadena de documentos nuevamente a través del method , encontrará que se fue.
Ha habido varias propuestas en el pasado para usar el operador de resolución de alcance de Ruby :: para acceder a los Method enlazados de objetos como este:
bound_method = obj::foo
Que se supone que es idéntico a
bound_method = obj.method(:foo)
El problema es que actualmente, se admite el uso del operador de resolución de alcance para los métodos de llamada :
obj.bar
obj::bar
# Hello
Y lo que es peor, esto se usa realmente, por lo que sería un cambio incompatible con los anteriores.