Как ссылаться на функцию в 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Обратите внимание, что
Procs называются иначе, чем методы:foo('Hello') # method foo.('Hello') # ProcСинтаксис
Обратите внимание, что важное различие между Rubyfoo.(bar)- это просто синтаксический сахар дляfoo.call(bar)(который дляProcs иMethods также называетсяfoo[bar]). Реализация методаcallна вашем объекте, а затем вызов его с помощью.()- это самое близкое, что вы получите от возможностей Python__call__.Procs и 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') # HelloUnboundMethodтолько к объекту, который является экземпляром модуля, из которого вы взяли метод. Вы не можете использовать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 # HelloMethod, и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 => nilgreet = 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)
