如何在Ruby中引用函数?

在python中,引用函数非常简单:

>>> def foo(): ... print "foo called" ... return 1 ... >>> x = foo >>> foo() foo called 1 >>> x() foo called 1 >>> x  >>> foo  

但是,它似乎在Ruby中有所不同,因为裸foo实际上调用了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 

我如何实际将函数 foo赋值给x然后调用它? 或者有更惯用的方法吗?

Ruby没有function。 它只有方法(不是一流的)和Proc ,它们是一流的,但不与任何对象相关联。

所以,这是一种方法:

 def foo(bar) puts bar end foo('Hello') # Hello 

哦,是的,这一个真正的方法,而不是顶级function或程序或其他东西。 在顶级定义的方法最终作为Object类中的私有(!)实例方法:

 Object.private_instance_methods(false) # => [:foo] 

这是一个Proc

 foo = -> bar { puts bar } foo.('Hello') # Hello 

请注意, Proc的调用与方法不同:

 foo('Hello') # method foo.('Hello') # Proc 

foo.(bar)语法只是foo.call(bar)语法糖(对于ProcMethod s,它也别名为foo[bar] )。 在你的对象上实现一个call方法然后用.()调用它是你最接近Python的__call__ ables。

请注意,Ruby Proc和Python lambdas之间的一个重要区别是没有限制:在Python中,lambda只能包含一个语句,但Ruby没有语句和表达式之间的区别( 一切都是表达式),所以这个限制根本就不存在,因此在很多情况下你需要在Python中传递一个命名函数作为参数,因为你不能在一个语句中表达逻辑,你会在Ruby中简单地传递一个Proc或者而是阻塞,因此甚至不会出现引用方法的丑陋语法问题。

您可以通过在Object#method上调用Object#method方法方法包装Method对象(实际上是鸭子类型Proc )中(这将为您提供一个其self绑定到该特定对象的方法):

 foo_bound = self.method(:foo) foo_bound.('Hello') # Hello 

您还可以使用Module#instance_method系列中的一种UnboundMethod从模块(或类,显然,因为类是一个模块) UnboundMethod#bind ,然后您可以将UnboundMethod#bind到特定对象并进行调用。 (我认为Python具有相同的概念,尽管有不同的实现:未绑定的方法只是明确地接受self参数,就像声明它的方式一样。)

 foo_unbound = Object.instance_method(:foo) # this is an UnboundMethod foo_unbound.('Hello') # NoMethodError: undefined method `call' for # foo_rebound = foo_unbound.bind(self) # this is a Method foo_rebound.('Hello') # Hello 

请注意,您只能将UnboundMethod绑定到一个对象,该对象是您从该方法获取的模块的实例。 您不能使用UnboundMethods在不相关的模块之间“移植”行为:

 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 

但请注意, MethodUnboundMethod都是方法的包装器而不是方法本身。 方法不是 Ruby中的对象。 (与我在其他答案中所写的相反,BTW。我真的需要回去修复它们。)你可以它们包装在对象中,但它们不是对象,你可以看到,因为你基本上都是这样的你总是得到包装的问题:身份和国家。 如果为同一方法多次调用method ,则每次都会得到一个不同的Method对象。 如果您尝试在该Method对象上存储某些状态(例如Python样式的__doc__字符串),那么该状态将对该特定实例是私有的,如果您尝试通过method再次检索文档字符串,您会发现它消失了。

过去有几个建议使用Ruby的作用域解析运算符::来访问对象的绑定Method ,如下所示:

 bound_method = obj::foo 

这应该是相同的

 bound_method = obj.method(:foo) 

问题是目前实际上支持使用范围解析运算符来调用方法:

 obj.bar obj::bar # Hello 

更糟糕的是,这实际上是使用的 ,所以这将是一个向后不兼容的变化。

您可以使用从Objectinheritance的method实例方法来检索Method对象,该对象本质上是一个可以调用callProc对象。

在控制台中,你这样做:

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

替代文字

Ruby支持proclambda ,在其他语言中可能被称为匿名函数或闭包,具体取决于它们的使用方式。 他们可能更接近你想要的。

从https://stackoverflow.com/a/26620095/226255复制的function和方法之间的(主要)差异

函数在类之外定义,而方法在类的内部和部分类中定义。

Ruby没有函数,你的def foo最终成为Object类的一个方法。

如果你坚持像上面那样定义foo ,你可以通过这样做来提取它的“function”:

 def foo(a,b) a+b end x = method(:foo).to_proc x.call(1,2) => 3 

说明:

 > method(:foo) # this is Object.method(:foo), returns a Method object bound to # object of type 'Class(Object)' => # method(:foo).to_proc # a Proc that can be called without the original object the method was bound to => # 

重要的提示:

to_proc “复制”方法的对象的关联实例变量(如果有)。 考虑一下:

 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 

从概念上讲,如果你想要一个可以在某种类型的对象上工作的“函数”,它应该是一个方法,你应该这样组织你的代码。 如果您只需要在某个上下文中使用“function”并希望传递它,请使用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)