Как мне ссылаться на функцию в 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 ответа
Руби не имеет функций. Он имеет только методы (которые не являются первоклассными) и Proc
s, которые являются первоклассными, но не связаны ни с каким объектом.
Итак, это метод:
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
foo.(bar)
синтаксис просто синтаксический сахар для foo.call(bar)
(который для Proc
с и Method
S также псевдоним foo[bar]
). Реализация call
метод на вашем объекте, а затем вызвать его с .()
это самая близкая вещь, которую вы получите к Python __call__
Ables.
Обратите внимание, что важное различие между Ruby Proc
Лямбда s и Python заключается в том, что ограничений нет: в 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
являются обертками вокруг метода, а не самого метода. Методы не являются объектами в Ruby. (Вопреки тому, что я написал в других ответах, кстати. Мне действительно нужно вернуться и исправить их.) Вы можете обернуть их в объекты, но они не являются объектами, и вы можете видеть это, потому что вы по сути получаете все то же самое проблемы, которые вы всегда получаете с обертками: личность и состояние. Если вы позвоните method
несколько раз для одного и того же метода, вы получите другой Method
возражать каждый раз. Если вы попытаетесь сохранить какое-то состояние на этом Method
объект (такой как стиль Python __doc__
например, строки), это состояние будет частным для данного конкретного экземпляра, и если вы попытаетесь снова получить строку документации через method
, вы обнаружите, что он ушел.
В прошлом было несколько предложений использовать оператор разрешения области видимости в Ruby. ::
чтобы получить доступ к Method
s таких объектов:
bound_method = obj::foo
Который должен быть идентичен
bound_method = obj.method(:foo)
Проблема в том, что в настоящее время фактически поддерживается использование оператора разрешения области для вызова методов:
obj.bar
obj::bar
# Hello
И что еще хуже, это на самом деле используется, так что это было бы несовместимым задом наперед.
Вы можете использовать method
метод экземпляра, унаследованный от Object
чтобы получить Method
объект, который по сути является Proc
объект, который вы можете вызвать call
на.
В консоли вы бы сделали это:
fooMethod = self.method(:foo) #fooMethod is a Method object
fooMethod.call #invokes fooMethod
Ruby поддерживает proc
а также lambda
которые в других языках могут называться анонимными функциями или замыканиями, в зависимости от того, как они используются. Они могут быть ближе к тому, что вы ищете.
Основное различие между функциями и методами, скопированное с /questions/34347932/v-chem-raznitsa-mezhdu-metodom-i-funktsiej/34347945#34347945
Функции определены вне классов, а методы определены внутри и внутри классов.
Рубин не имеет функций и ваш 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)