Методы в Ruby: объекты или нет?
Вдохновленный этим обсуждением, после некоторого поиска в Google я не смог найти ответ на довольно простой вопрос относительно методов в Ruby: являются ли методы объектами или нет?
Существуют разные мнения здесь и там, и мне бы очень хотелось услышать, скажем, подробное объяснение.
Я знаю метод Object#method
, который принимает имя метода и возвращает экземпляр Method
, но, с другой стороны, есть аналогичная вещь, которую вы можете сделать с блоками, чтобы сделать их в экземпляры Proc
, а блоки не являются объектами, поэтому что делает методы разными?
Ответы
Ответ 1
Методы являются фундаментальной частью синтаксиса Ruby, но они не являются значениями, с которыми могут работать программы Ruby. То есть методы Ruby не являются объектами в том виде, в котором они представляют собой строки, числа и массивы. Однако возможно получить объект метода, который представляет данный метод, и мы можем вызывать методы косвенно через объекты метода.
Из языка программирования Ruby:
![alt text]()
Ответ 2
Вы не можете сказать.
Единственный способ получить доступ к методу - отправить сообщение #method
на некоторый объект, который затем вернет объект Method
. Но является ли объект Method
объектом самого метода? Или это оболочка вокруг метода? Или это преобразованная версия исходного метода?
Вы не можете знать: если вы хотите посмотреть на метод, вы должны вызвать #method
, после чего вы обязательно получите объект. То, что было до того, как вы назвали #method
, вы не можете смотреть, поэтому вы не можете сказать.
Пара данных: в Ruby все возвращает значение. Что возвращает def
? Он всегда возвращает nil
, а не объект Method
. И define_method
? Он возвращает Proc
, но не Method
(а не UnboundMethod
). [Примечание: в Rubinius def
возвращает скомпилированный байт-код метода, но все же не объект Method
.]
Если вы посмотрите на 4-й и 5-й параграфы раздела 6.1 спецификации языка Ruby (строки 29-34 и 1-5 на страницах 5 и 6), вы можете ясно видеть, что существует различие между методами и объектами, И если вы посмотрите на спецификацию встроенных классов, вы обнаружите, что там не Method
и UnboundMethod
, равно как и Object#method
. IOW: вы можете построить полностью совместимый с стандартами Ruby-интерпретатор, в котором методы не являются объектами.
Теперь блоки OTOH определенно не являются объектами. Существует множество способов создания объектов Proc
из блоков, которые затем имеют такое же поведение, что и исходный блок (lambda
, Proc
, Proc.new
, &
sigil), но сами блоки не являются объектами.
Подумайте об этом так: вы можете передать строку в File.new
для создания объекта файла, но это не делает строку файлом. Вы можете передать блок в Proc.new
для создания объекта proc, но это не делает блок proc.
Ответ 3
В Ruby методы и блоки сами по себе не являются собственными или первоклассными объектами. Однако их можно легко обернуть в объекты, так что это обычно не имеет значения.
Но попробуйте, и имейте в виду результат,
a = Object.method(:new).object_id
b = Object.method(:new).object_id
a == b
=> false
В Haskell все значения (включая числа, а также lambdas и функции) являются значениями первого класса. Во всех аспектах языка все они рассматриваются одинаково. Это не относится к Ruby, но может быть аппроксимировано.
Ответ 4
Объекты и методы не совпадают, даже если возвращаемое значение для методов является объектом, а не нулем.
Объекты живут в куче, если только в методе, лямбда или области proc и сам метод не живет в стеке и имеет назначение адреса после интерпретации, в то время как статические объекты и объекты класса выделяются в куче. Ruby все еще использует C, чтобы интерпретировать его и передать его в структуру VALUE.
Ответ 5
Поскольку скобки являются необязательными в ruby, объекты метода обычно "скрыты" в том смысле, что вам нужно явно получить объект метода с помощью метода method
. Однако, если вы сделаете попытку захватить объект метода, становится совершенно ясно, что он действует как объект. Поскольку Ruby >= 2.1, это легче использовать, чем когда-либо.
Например, вы можете заставить свои методы вести себя так же, как в Javascript (где нет объекта parens, а метод parens используется для вызова метода):
foo = method def foo
def a(num)
3 * num.to_i
end
n = yield if block_given?
a(n || 3)
rescue
"oops!"
end
def foo.bar(num)
a(num)
end
foo.class #=> Method
foo() #=> 9
foo.call #=> 9
foo.call{2} #=> 6
foo(){2} #=> 6
foo.call{ raise "blam!" } #=> "oops!"
foo.bar(5) #=> 15
Посмотрите этот gist для версии с этим примером, написанным как тесты.
JRL answer цитирует книгу Маца о том, что методы - это не объекты, такие как строки и т.д., но объекты метода являются реальными и не похожи на объекты parens/no-parens они действуют так же, как и любой другой рубиновый объект. Это язык с утиным языком, поэтому я бы сказал, что квалифицирует методы как объекты в моей книге.
Ответ 6
В Ruby методы не являются объектами. Это сбивает с толку, потому что есть класс Method, и вы можете получить экземпляры метода. Эти экземпляры являются просто прокси для самого метода. Эти экземпляры предоставляют некоторые полезные функции. У них есть некоторая внутренняя магия, которая связывает их с реальным методом (так что вы можете делать такие вещи, как Method#call
), но вы не можете получить к нему доступ (AFAIK).
1.method(:to_s).object_id == 1.method(:to_s).object_id #=> false
Это означает, что либо 1
имеет два метода #to_s
(чего нет), либо то, что возвращается методом #method
самом деле не сам метод, а некоторый прокси для метода. Если бы методы были на самом деле объектами, у вас бывали ситуации, когда вы могли получить один и тот же экземпляр дважды. Если бы методы были объектами, то вы могли бы делать такие вещи, как установить для них переменную экземпляра, а затем они получить значение этой переменной экземпляра после второго извлечения объекта метода. Вы не можете сделать это. Таким образом, хотя это, как правило, не имеет значения, бывают ситуации, когда я не могу делать то, что хотел бы.
1.method(:to_s).instance_variable_set(:@foo, 'foo') #=> "foo"
1.method(:to_s).instance_variable_get(:@foo) #=> nil
# And just in case you question it...
1.object_id == 1.object_id #=> true