Ruby send vs __send__

Я понимаю концепцию some_instance.send, но я пытаюсь понять, почему вы можете назвать это двумя способами. Ruby Koans подразумевает, что есть некоторая причина, помимо предоставления множества разных способов сделать то же самое. Вот два примера использования:

class Foo
  def bar?
    true
  end
end

foo = Foo.new
foo.send(:bar?)
foo.__send__(:bar?)

Кто-нибудь есть об этом?

Ответ 1

Некоторые классы (например, стандартный класс сокетов библиотеки) определяют свой собственный метод send, который не имеет ничего общего с Object#send. Поэтому, если вы хотите работать с объектами любого класса, вам нужно использовать __send__, чтобы быть в безопасности.

Теперь возникает вопрос, почему существует send, а не только __send__. Если бы было только __send__, имя send могло бы использоваться другими классами без какой-либо путаницы. Причиной этого является то, что send существовал первым, и только позже было осознано, что имя send также может быть полезно использовать в других контекстах, поэтому __send__ был добавлен (то же самое, что произошло с id и object_id кстати).

Ответ 2

Если вам действительно нужно send вести себя так, как обычно, вы должны использовать __send__, потому что он не будет (он не должен) быть переопределенным. Использование __send__ особенно полезно при метапрограммировании, когда вы не знаете, какие методы контролирует класс. Это могло бы превысить send.

Часы:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

foo = Foo.new
foo.send(:bar?)
# => false
foo.__send__(:bar?)
# => true

Если вы переопределите __send__, Ruby выдаст предупреждение:

предупреждение: переопределение `__send__ 'может вызывают серьезные проблемы.

В некоторых случаях, когда было бы полезно переопределить send, было бы подходящим это имя, например, передача сообщений, классы сокетов и т.д.

Ответ 3

__send__ существует, поэтому он не может быть переписан случайно.

Что касается того, почему существует send: я не могу говорить никому, но object.send(:method_name, *parameters) выглядит лучше, чем object.__send__(:method_name, *parameters), поэтому я использую send, если мне не нужно использовать __send__.

Ответ 4

Помимо того, что вам уже говорили другие, и что сводится к утверждению, что send и __send__ являются двумя псевдонимами того же метода, вас может заинтересовать третья, иногда другая возможность, которая public_send, Пример:

A, B, C = Module.new, Module.new, Module.new
B.include A #=> error -- private method
B.send :include, A #=> bypasses the method privacy
C.public_send :include, A #=> does not bypass privacy

Обновление. Поскольку методы Ruby 2.1, Module#include и Module#extend становятся общедоступными, поэтому приведенный выше пример больше не работает.

Ответ 5

Основное различие между send, __send__ и public_send заключается в следующем.

  1. send и __send__ технически совпадают с используемыми для вызова метода Object, но основное отличие состоит в том, что вы можете переопределить метод send без какого-либо предупреждения, а при переопределении __send__ появляется предупреждающее сообщение

предупреждение: переопределение __send__ может вызвать серьезные проблемы

Это связано с тем, что во избежание конфликтов, особенно в гемах или библиотеках, когда контекст, в котором он будет использоваться, неизвестен, всегда используйте __send__ вместо send.

  1. Разница между send (или __send__) и public_send заключается в том, что send/__send__ может вызывать объекты private, а public_send cant.
class Foo
   def __send__(*args, &block)
       "__send__"
   end
   def send(*args)
     "send"
   end
   def bar
       "bar"
   end
   private
   def private_bar
     "private_bar"
   end
end

Foo.new.bar #=> "bar"
Foo.new.private_bar #=> NoMethodError(private method 'private_bar' called for #Foo)

Foo.new.send(:bar) #=> "send"
Foo.new.__send__(:bar) #=> "__send__"
Foo.new.public_send(:bar) #=> "bar"

Foo.new.send(:private_bar) #=> "send"
Foo.new.__send__(:private_bar) #=> "__send__"
Foo.new.public_send(:private_bar) #=> NoMethodError(private method 'private_bar' called for #Foo)

В конце попытайтесь использовать public_send, чтобы избежать прямого вызова приватного метода вместо использования __send__ или send.