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