ruby ruby-1.9 first-class-functions

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


Puede utilizar el method instancia de método heredado de Object para recuperar un objeto de Method , que esencialmente es un objeto Proc que puede invocar la call .

En la consola, harías esto:

fooMethod = self.method(:foo) #fooMethod is a Method object fooMethod.call #invokes fooMethod


Ruby admite Proc y lambda que, en otros idiomas, podrían denominarse funciones anónimas o cierres, según cómo se utilicen. Podrían estar más cerca de lo que estás buscando.


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.