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

Ответ 1

Руби не имеет функций. Он имеет только методы (которые не являются первоклассными) и 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) (который для Proc и Method также имеет псевдоним foo[bar]). Реализация метода call на вашем объекте, а затем вызов его с помощью .() - самая близкая вещь, которую вы получите к __call__ Python __call__.

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

Вы можете обернуть метод в объект Method (который по сути является утилитой Proc), вызвав метод Object#method для объекта (который даст вам 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

Обратите внимание, что вы можете привязать 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 и Method UnboundMethod являются обертками вокруг метода, а не самого метода. Методы не являются объектами в Ruby. (Вопреки тому, что я написал в других ответах, кстати. Мне действительно нужно вернуться и исправить их.) Вы можете обернуть их в объекты, но они не являются объектами, и вы можете видеть это, потому что вы по сути получаете все то же самое проблемы, которые вы всегда получаете с обертками: личность и состояние. Если вы вызываете method несколько раз для одного и того же метода, вы каждый раз получаете новый объект Method. Если вы попытаетесь сохранить какое-либо состояние для этого объекта Method (например, строки __doc__ стиле Python), это состояние будет частным для этого конкретного экземпляра, и если вы попытаетесь снова получить строку документации через method, вы обнаружите, что это прошло.

Существует также синтаксический сахар в виде оператора ссылки на метод .: ::

bound_method = obj.:foo

Который идентичен

bound_method = obj.method(:foo)

Ответ 2

Вы можете использовать метод экземпляра method, унаследованный от Object, чтобы получить method, который по существу является объектом Proc, который вы можете вызвать call on.

В консоли вы сделаете следующее:

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

fooMethod.call #invokes fooMethod

alt text

Ответ 3

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

Ответ 4

(главное) различие между функциями и методами, скопированными из fooobar.com/info/738/...

Функции определяются вне классов, а методы определены внутри и части классов.

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)