Как ссылаться на функцию в 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 24

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

Синтаксис foo.(bar) - это просто синтаксический сахар для foo.call(bar) (который для Procs и Methods также называется foo[bar]). Реализация метода call на вашем объекте, а затем вызов его с помощью .() - это самое близкое, что вы получите от возможностей Python __call__.

Обратите внимание, что важное различие между Ruby 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')
# 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 чтобы получить a Method объект, который по существу является Proc объект, на который вы можете вызвать call.

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

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

fooMethod.call #invokes fooMethod

текст Alt

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

(основное) различие между функциями и методами, скопированными из 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)