Как ссылаться на функцию в 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
фактически вызывает фу:
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
О, И, да, это реальныйметод, а не функция верхнего уровня или процедура или что-то еще. Методы, определенные на верхнем уровне, заканчиваются как частные (!) методы экземпляра в классе
Object
:Object.private_instance_methods(false) # => [:foo]
Это
Proc
:foo = -> bar { puts bar } foo.('Hello') # Hello
Обратите внимание, что
Proc
s называются иначе, чем методы:foo('Hello') # method foo.('Hello') # Proc
Синтаксис
Обратите внимание, что важное различие между Rubyfoo.(bar)
- это просто синтаксический сахар дляfoo.call(bar)
(который дляProc
s иMethod
s также называетсяfoo[bar]
). Реализация методаcall
на вашем объекте, а затем вызов его с помощью.()
- это самое близкое, что вы получите от возможностей Python__call__
.Proc
s и Python lambdas заключается в том, что нет никаких ограничений: в Python лямбда может содержать только один оператор, но Ruby у Нет различия между операторами и выражениями (все является выражением), и поэтому этого ограничения просто не существует, поэтому во многих случаях, когда вам нужно передать именованную функцию в качестве аргумента в Python, потому что вы не можете выразить логику в одном операторе, вы бы в Ruby просто передалиProc
или блок, так что проблема уродливого синтаксиса для ссылок на методы даже не возникает.Вы можете оберните метод в объект
Method
(который по сути является утиным типом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
являются оболочками вокруг метода, не сам метод. Методы не объекты в Рубине. (Вопреки тому, что я написал в других ответах, кстати. Мне действительно нужно вернуться и ПОЧИНИТЬ ИХ.) Вы можете обернуть их в объекты, но они не являются объектами, и вы можете видеть, что, поскольку вы по существу получаете все те же проблемы, которые вы всегда получаете с оболочками: идентичность и состояние. Если вы вызываетеmethod
несколько раз для одного и того же метода, вы каждый раз будете получать другой объектMethod
. Если вы пытаетесь сохранить некоторое состояние на этом объектеMethod
(например Строки в стиле Python__doc__
, например), это состояние будет закрыто для этого конкретного экземпляра, и если вы попытаетесь снова получить свою строку docstring черезmethod
, вы обнаружите, что она исчезла.В прошлом было несколько предложений использовать оператор разрешения области видимости Ruby
::
для доступа к связаннымMethod
объектам, подобным этому:bound_method = obj::foo
Который должен быть идентичен
bound_method = obj.method(:foo)
Проблема заключается в том, что в настоящее время, используя оператор разрешения области для вызова методов фактически поддерживается:
obj.bar obj::bar # Hello
И что еще хуже, это фактически используется, так что это было бы обратно несовместимое изменение.
Можно использовать метод экземпляра
method
, унаследованный отObject
чтобы получить aMethod
объект, который по существу являетсяProc
объект, на который вы можете вызватьcall
.В консоли вы сделаете следующее:
fooMethod = self.method(:foo) #fooMethod is a Method object fooMethod.call #invokes fooMethod
(основное) различие между функциями и методами, скопированными из 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
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)