如何在Ruby中引用一个函数?

36

在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>

不过,在 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并调用它?或者是否有更加惯用的方法可以实现这个需求?


1
请查看这篇文章http://www.khelll.com/blog/ruby/ruby-dynamic-method-calling/,因为它提供了几种动态调用方法的方式。 - khelll
1
“一个裸的foo实际上调用了foo”,是的,Ruby虽然不坚持使用括号,但我使用它们来在视觉上将参数与后面的任何内容分开。这只是习惯而已。 - the Tin Man
4个回答

62

Ruby没有函数,它只有方法(这些方法不是一等公民)和Proc(一等公民),但不与任何对象相关联。

因此,这是一个方法:

def foo(bar) puts bar end

foo('Hello')
# Hello

哦,还有,是的,这 确实 是一个方法,而不是顶级函数或过程之类的东西。在顶层定义的方法最终会成为 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也被别名为foo[bar])。在您的对象上实现一个call方法,然后使用.()调用它是您可以获得类似Python中__call__可调用对象的最接近的东西。
请注意,Ruby Proc和Python lambda之间的一个重要区别是没有限制:在Python中,lambda只能包含单个语句,但Ruby没有语句和表达式之间的区别(一切都是表达式),因此这种限制根本不存在,在许多情况下,您需要将命名函数作为参数传递给Python,因为您无法在单个语句中表达逻辑,您可以简单地传递Proc或块,在Ruby中,这样问题就不会出现丑陋的语法引用方法。
您可以通过在对象上调用Object#method方法(这将为您提供其self绑定到该特定对象的Method)来在Method对象中包装一个方法(本质上是鸭子类型Proc)。
foo_bound = method(:foo)

foo_bound.('Hello')
# Hello

你也可以使用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

请注意,您只能将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
请注意,无论是Method还是UnboundMethod都是方法的包装器,而不是方法本身。在Ruby中,方法不是对象(顺便说一下,这与我在其他答案中写的相反。我真的需要回去修复那些)。你可以将它们封装到对象中,但它们不是对象,因为你基本上会遇到所有同样的包装器问题:标识和状态。如果您多次调用相同方法的method,每次都会得到不同的Method对象。如果你试图在Method对象上存储一些状态(例如Python风格的__doc__字符串),该状态将对特定的实例是私有的,如果您尝试通过method再次检索文档字符串,您会发现它已经不存在了。
此外,还有一个形式为方法引用运算符.:的语法糖:
bound_method = obj.:foo

这与...相同

bound_method = obj.method(:foo)

1
好的回答,像往常一样 :-) 我不太同意的一点是方法的身份识别,它比你所暗示的要更好处理。是的,你会得到不同的对象,但它们彼此之间是==相等的,即使是通过respond_to_missing?获得的奇怪方法也是如此。 - Marc-André Lafortune
@guettli:我认为这是一种权衡。在Python中,您可以通过省略括号来引用方法,但另一方面,您不能在没有括号的情况下调用它们。在Ruby中,您可以在没有括号的情况下调用它们,因此您需要使用不同的方式来引用它们,而在面向对象的语言中获取方法引用的更好方法是调用一个请求引用的方法。 - Jörg W Mittag
1
Ruby中方法可无需括号调用的重要部分在于它有助于实施统一访问原则:也就是说,在Ruby中所有成员访问都是通过方法进行的;在Python中,直接获取整数成员(例如)将是obj.member,而调用返回整数的方法是obj.member();而在Ruby中,则始终是一个方法。这意味着您可以从“简单”的访问器(例如attr_accessor提供的访问器)更改为更复杂的实现,而不必更改调用者的使用方式。 - philomory
如果这回答了OP的问题,那么它是不清楚的。这是最后的obj.:foo示例吗? - siimphh

10

你可以使用从Object继承的method实例方法来检索一个Method对象,它实际上是一个Proc对象,你可以调用call方法。

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

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

fooMethod.call #invokes fooMethod

alt text


“self” 不是必需的。x = method(:foo); x.call 可以正常工作。 - steenslag
2
@steenslag 有时候啰嗦一点也没关系,你是对的 :) - Jacob Relkin
1
@Jason main 是每个 Ruby 应用程序的主执行上下文,如果您愿意,它是整个应用程序生命周期的包装类。 - Jacob Relkin
3
我刚想到一个更好的解释方式,通过对比self在其他情境中的用法来说明:在方法内部,每当该方法被调用时,self会动态绑定到接收者。在模块定义体中,self是指模块本身。(这就是为什么def self.foo可以定义"类"方法的原因。)在脚本体内(基本上指文件内但不包括任何其他类型的体内),selfmain。(我认为这也适用于通过-e从命令行传递的字符串。) - Jörg W Mittag
1
特殊情况:在一个块中,self 是词法作用域的,即块内部的 self 是块外部的 self,但是当一个块被传递给 instance_execinstance_eval 时,块内的 self 实际上是 instance_execinstance_eval 的接收者(这也是应该避免使用这两种方法的原因之一)。 - Jörg W Mittag
显示剩余5条评论

1

Ruby支持 proc lambda,在其他语言中可能被称为匿名函数或闭包,这取决于它们的使用方式。它们可能更接近您寻找的功能。


1

https://dev59.com/pXVC5IYBdhLWcg3w4VVz#26620095复制的内容,函数和方法的主要区别是:

函数定义在类外部,而方法定义在类内部并作为类的一部分。

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

如果您坚持像上面那样定义foo,可以通过以下方式提取其“功能”:

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: 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)>

重要提示:

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

从概念上讲,如果你想要一个能够处理某种类型对象的"函数",它应该是一个方法,并且你应该按照这样的方式组织你的代码。如果你只需要在特定的上下文中使用你的"函数"并希望传递它,请使用lambda表达式:

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接