如何在Ruby中引用函数?

Jas*_*ker 28 ruby first-class-functions ruby-1.9

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

>>> 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>
Run Code Online (Sandbox Code Playgroud)

但是,它似乎在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 
Run Code Online (Sandbox Code Playgroud)

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

Jör*_*tag 52

Ruby没有功能.它只有方法(不是第一类)和Procs,它们是一流的,但不与任何对象相关联.

所以,这是一种方法:

def foo(bar) puts bar end

foo('Hello')
# Hello
Run Code Online (Sandbox Code Playgroud)

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

Object.private_instance_methods(false) # => [:foo]
Run Code Online (Sandbox Code Playgroud)

这是一个Proc:

foo = -> bar { puts bar }

foo.('Hello')
# Hello
Run Code Online (Sandbox Code Playgroud)

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

foo('Hello')  # method
foo.('Hello') # Proc
Run Code Online (Sandbox Code Playgroud)

foo.(bar)语法只是语法糖foo.call(bar)(这对于ProcS和Methods的也别名foo[bar]).call在你的对象上实现一个方法,然后调用它.()是最接近Python的人__call__.

需要注意的是红宝石之间的重要区别ProcS和Python的lambda表达式的是,没有任何限制:在Python中,一个lambda只能包含一个语句,但是Ruby不具有语句和表达式(区分一切是一个表达式),和所以这个限制根本不存在,因此在很多情况下你需要在Python中传递一个命名函数作为参数,因为你不能在一个语句中表达逻辑,你会在Ruby中简单地传递一个Proc或一个块来代替因此,甚至不会出现引用方法的丑陋语法问题.

你可以一个方法Method对象(基本上鸭类型Proc调用)Object#method的对象上的方法(这将给你一个Method,其self被绑定到特定的对象):

foo_bound = method(:foo)

foo_bound.('Hello')
# Hello
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

请注意,您只能将一个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
Run Code Online (Sandbox Code Playgroud)

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

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

bound_method = obj.:foo
Run Code Online (Sandbox Code Playgroud)

哪个应该是相同的

bound_method = obj.method(:foo)
Run Code Online (Sandbox Code Playgroud)

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

def foo(bar) puts bar end

foo('Hello')
# Hello
Run Code Online (Sandbox Code Playgroud)

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


Jac*_*kin 9

您可以使用method从中继承的实例方法Object来检索Method对象,该对象本质上是Proc您可以调用的对象call.

在控制台中,您可以这样做:

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

fooMethod.call #invokes fooMethod
Run Code Online (Sandbox Code Playgroud)

替代文字

  • 我只想到一个更好的方法来解释它,通过将它与`self`的其他出现形成对比:在一个方法中,每当调用方法时,`self`将动态地绑定到接收器.在模块定义体中,`self`是模块本身.(这就是为什么`def self.foo`用于定义"类"方法.)在一个脚本体(基本上意味着在一个文件中,但在任何其他类型的体外),`self`是`main`.(我认为这也适用于从命令行通过`-e`传递的字符串.) (3认同)
  • @steenslag有时你知道,有点冗长是可以的.顺便说一下你是对的:) (2认同)