Как мне ссылаться на функцию в Ruby? - PullRequest
26 голосов
/ 28 ноября 2010

В 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 ]

52 голосов
/ 28 ноября 2010

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

Обратите внимание, что важное различие между Ruby Proc s и PythonЛямбда в том, что ограничений нет: в Python лямбда может содержать только один оператор, но в Ruby нет различий между операторами и выражениями ( все является выражением)и поэтому это ограничение просто не существует, поэтому во многих случаях, когда вам нужно передать именованную функцию в качестве аргумента в Python, потому что вы не можете выразить логику в одном выражении, вы бы вВместо этого Ruby просто передает Proc или блок, так что проблема уродливого синтаксиса для ссылок на методы даже не возникает.

Вы можете обернуть метод в Method объект (который, по сути, является утиным типом Proc), вызывая метод Object#method объекта (который даст вам Method, self которого связан с этим рсуставной объект):

foo_bound = method(:foo)

foo_bound.('Hello')
# Hello

Вы также можете использовать один из методов семейства Module#instance_method, чтобы получить UnboundMethod из модуля (или класса, очевидно, поскольку класс is-module), который вы можете затем 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 (например, например, строки __doc__ в стиле Python), это состояние будет приватным для этого конкретного экземпляра , и если вы попытаетесь получитьВаша строка документации снова через method, вы обнаружите, что она исчезла.

Существует также синтаксический сахар в виде оператора ссылки на метод .::

bound_method = obj.:foo

Что идентично

bound_method = obj.method(:foo)
9 голосов
/ 28 ноября 2010

Вы можете использовать метод экземпляра method, унаследованный от Object, чтобы получить объект Method, который по сути является Proc объект, который вы можете вызвать call on.

В консоли вы бы сделали это:

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

fooMethod.call #invokes fooMethod

alt text

1 голос
/ 28 ноября 2010

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

0 голосов
/ 19 ноября 2014

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