Как мне ссылаться на функцию в 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)
Другие вопросы по тегам