Как ссылаться на функцию в Ruby?

в 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, а затем назвать его? Или есть более идиоматичный способ сделать это?

4 ответов


Ruby не имеет функций. Он имеет только методы (которые не являются первоклассными) и ProcС первого класса, но не связаны с каким-либо объектом.

Итак, это метод:

def foo(bar) puts bar end

foo('Hello')
# Hello

О, И да, это is a реальные метод, а не функция или процедура или что-то. Методы, определенные на верхнем уровне, заканчиваются как частные (!) методы экземпляра в Object класс:

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

это Proc:

foo = -> bar { puts bar }

foo.('Hello')
# Hello

обратите внимание, что Procs вызываются иначе, чем методы:

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

на foo.(bar) синтаксис-это просто синтаксический сахар для foo.call(bar) (который для Procs и Methods также имеет псевдоним foo[bar]). Реализации call метод на вашем объекте, а затем вызов его с помощью .() это самое близкое, что вы получите к Python в __call__ables.

обратите внимание, что важное различие между Ruby Procs и Python lambdas разве что ограничений нет: в Python лямбда может содержать только один оператор, но Ruby не есть различие между утверждениями и выражениями (все это выражение), и поэтому данное ограничение просто не существует, поэтому во многих случаях, когда вы нужно чтобы передать именованную функцию в качестве аргумента в Python, потому что вы не можете выразить логику в одном операторе, вы бы в Ruby просто передали Proc или a блок вместо этого, так что проблема уродливого синтаксиса для ссылок на методы даже не возникает.

вы можете wrap метод Method объект (который по существу duck-types Proc) путем вызова Object#method метод на объекте (который даст вам Method чей self привязан к этому конкретному объекту):

foo_bound = self.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 are фантики вокруг метода, не сам метод. Методы не объекты в Ruby. (Вопреки тому, что я написал в других ответах, кстати. Мне действительно нужно вернуться и исправить их.) Вы можете wrap их в объектах, но они не объекты, и вы можете видеть это, потому что вы по существу получаете все те же проблемы вы всегда получить с обертками: идентичность и состояние. Если вы позвоните method несколько раз для одного и того же метода, вы получите другой


можно использовать method метод экземпляра, унаследованный от Object для получения Method


Ruby поддерживает proc и lambda которые на других языках могут называться анонимными функциями или закрытиями, в зависимости от того, как они используются. Они могут быть ближе к тому, что вы ищете.


(основная) разница между функциями и методами, скопированными из 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)