¿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.