Методы в 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