如何在Ruby中引用函数?

时间:2010-11-27 23:27:08

标签: ruby ruby-1.9 first-class-functions

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

但是,由于裸foo实际上调用了foo:

,因此在Ruby中似乎有所不同
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然后调用它?或者有更惯用的方法吗?

4 个答案:

答案 0 :(得分:52)

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

所以,这是一种方法:

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__ ables的方法。

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

你可以通过在一个对象上调用Method方法来包装一个Proc对象中的方法(基本上是鸭子类型Object#method)给你一个Method self绑定到该特定对象:

foo_bound = self.method(:foo)

foo_bound.('Hello')
# Hello

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

UnboundMethod#bind

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

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

Method

应该与

相同
bound_method = obj::foo

问题在于,目前实际上支持使用调用方法的范围解析运算符:

bound_method = obj.method(:foo)

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

答案 1 :(得分:9)

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

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

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

fooMethod.call #invokes fooMethod

alt text

答案 2 :(得分:1)

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

答案 3 :(得分:0)

https://stackoverflow.com/a/26620095/226255

复制的函数和方法之间的(主要)差异
  

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

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

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