代码之家  ›  专栏  ›  技术社区  ›  Jason Baker

如何在Ruby中引用函数?

  •  26
  • Jason Baker  · 技术社区  · 14 年前

    在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 
    

    我该如何分配 功能 然后打给x?或者有更习惯的方法来做这件事吗?

    4 回复  |  直到 14 年前
        1
  •  52
  •   Jörg W Mittag    5 年前

    Ruby没有函数。它只有方法(不是一流的)和 Proc 是第一类的,但不与任何对象关联。

    所以,这是一种方法:

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

    哦,还有,是的,这个 真实的 方法,而不是顶级函数或过程之类的。在顶层定义的方法最终被定义为private(!)中的实例方法 Object 班级:

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

    这是一个 过程 :

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

    请注意 过程 s的调用与方法不同:

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

    这个 foo.(bar) 语法只是 foo.call(bar) (用于 过程 s和 Method s也别名为 foo[bar] ). 实现 call 方法,然后用 .() 是最接近Python的 __call__ 艾布尔斯。

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

    你可以 中的方法 方法 对象(本质上是duck类型 过程 )打电话给 Object#method 对象上的方法(它将为您提供 方法 谁的 self 绑定到该特定对象):

    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
    

    注意,您只能绑定 无界法 对象,该对象是从中获取方法的模块的实例。你不能使用 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 对于同一方法多次,您将得到不同的 方法 每次都反对。如果你想在上面存储一些状态 方法 对象(例如Python样式 __doc__ 例如,字符串),该状态对 那个特别的 实例,如果您尝试通过 方法 ,你会发现它不见了。

    也有语法糖的形式 方法引用运算符 .: :

    bound_method = obj.:foo
    

    bound_method = obj.method(:foo)
    
        2
  •  9
  •   Jacob Relkin    14 年前

    你可以使用 method 实例方法继承自 Object 检索 Method 对象,本质上是 Proc 可以调用的对象 call 打开。

    在控制台中,您可以执行以下操作:

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

    alt text

        3
  •  1
  •   the Tin Man    14 年前

    Ruby支持 proc lambda 在其他语言中,根据它们的使用方式,可以将其称为匿名函数或闭包。它们可能更接近你要找的东西。

        4
  •  0
  •   Community CDub    7 年前

    从中复制的函数和方法之间的(主要)差异 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)