Почему ruby ​​не поддерживает перегрузку методов?


146

Вместо поддержки перегрузки методов Ruby перезаписывает существующие методы. Кто-нибудь может объяснить, почему язык был разработан таким образом?

Ответы:


166

Перегрузка методов может быть достигнута путем объявления двух методов с одинаковыми именами и разными сигнатурами. Эти разные подписи могут быть

  1. Аргументы с разными типами данных, например: method(int a, int b) vs method(String a, String b)
  2. Переменное количество аргументов, например: method(a) vs method(a, b)

Мы не можем добиться перегрузки метода, используя первый способ, потому что в ruby ​​( динамически типизированный язык ) нет объявления типа данных . Таким образом, единственный способ определить вышеупомянутый методdef(a,b)

При втором варианте может показаться, что мы можем добиться перегрузки метода, но мы не можем. Допустим, у меня есть два метода с разным количеством аргументов,

def method(a); end;
def method(a, b = true); end; # second argument has a default value

method(10)
# Now the method call can match the first one as well as the second one, 
# so here is the problem.

Таким образом, ruby ​​должен поддерживать один метод в цепочке поиска методов с уникальным именем.


22
Ответ @ Jörg W Mittag, похороненный далеко внизу, определенно стоит прочитать.
user2398029 25.11.12

1
И ответ @Derek Ekins, похороненный еще дальше, дает альтернативу
Кирилл Дюшон-Дорис

Обратите внимание на будущих рубистов ... FWIW вы можете сделать это с помощью контракта gem egonschiele.github.io/contracts.ruby/#method-overloading
engineerDave

214

«Перегрузка» - это термин, который просто не имеет смысла в Ruby. Это в основном является синонимом «статического аргумента на основе отправки», но Рубин не имеет статическую отправку вообще . Итак, причина, по которой Ruby не поддерживает статическую диспетчеризацию на основе аргументов, заключается в том, что она не поддерживает статическую диспетчеризацию, точка. Он не поддерживает статическую диспетчеризацию любого типа , основанную на аргументах или иным образом.

Теперь, если вы не на самом деле конкретно спрашивать о перегрузке, но , может быть , о динамическом аргументе на основе отправки, то ответ: потому что Мацы не реализовали его. Потому что никто не удосужился предложить это. Потому что никто не удосужился реализовать это.

В целом, динамическая диспетчеризация на основе языка в языке с необязательными аргументами и списками аргументов переменной длины очень трудно понять правильно, и еще труднее сделать ее понятной. Даже в языках со статической диспетчеризацией, основанной на аргументах, и без дополнительных аргументов (например, в Java), иногда почти невозможно сказать для простого смертного, какая перегрузка будет выбрана.

В C # вы можете фактически закодировать любую проблему 3-SAT в разрешение перегрузки, что означает, что разрешение перегрузки в C # является NP-сложным.

Теперь попробуйте это с динамической диспетчеризацией, где у вас есть дополнительное измерение времени, которое нужно держать в голове.

Существуют языки, которые динамически распределяются на основе всех аргументов процедуры, в отличие от объектно-ориентированных языков, которые отправляют только по «скрытому» нулевому selfаргументу. Common Lisp, например, отправляет динамические типы и даже динамические значения всех аргументов. Clojure рассылает произвольную функцию от всех аргументов (что, кстати, чрезвычайно круто и чрезвычайно мощно).

Но я не знаю ни одного языка OO с динамической диспетчеризацией на основе аргументов. Мартин Одерски сказал, что он мог бы рассмотреть возможность добавления диспетчеризации на основе аргументов в Scala, но только если он может одновременно устранить перегрузку и быть обратно совместимым как с существующим кодом Scala, который использует перегрузку, так и с Java (особенно он упомянул Swing и AWT). которые разыгрывают очень сложные трюки, выполняющие практически все неприятные темные ситуации с довольно сложными правилами перегрузки Java). У меня были некоторые идеи по поводу добавления рассылки на основе аргументов в Ruby, но я никогда не мог понять, как сделать это обратно совместимым способом.


5
Это правильный ответ. Принятый ответ упрощает. C # имеет именованные параметры и необязательные параметры и все еще реализует перегрузку, поэтому это не так просто, как « def method(a, b = true)не будет работать, поэтому перегрузка методов невозможна». Это не; это просто сложно. Однако я нашел ЭТОТ ответ действительно информативным.
tandrewnichols

1
@tandrewnichols: просто для того, чтобы дать некоторое представление о том, насколько «сложное» разрешение перегрузки в C #… можно закодировать любую проблему 3-SAT в качестве разрешения перегрузки в C #, и компилятор может решить ее во время компиляции, создавая таким образом разрешение перегрузки в C # NP. -hard (3-SAT известен как NP-полный). Теперь представьте, что нужно делать это не один раз для каждого сайта вызова во время компиляции, а один раз для каждого вызова метода для каждого вызова метода во время выполнения.
Йорг Миттаг,

1
@ JörgWMittag Не могли бы вы включить ссылку, показывающую кодировку проблемы 3-SAT в механизме разрешения перегрузки?
Squidly


2
Не похоже, что «перегрузка» является синонимом «статической рассылки на основе аргументов». Статическая диспетчеризация на основе аргументов является просто самой распространенной реализацией перегрузки. Перегрузка - это независимый от реализации термин, означающий «одно и то же имя метода, но разные реализации в одной и той же области».
сновиты

85

Я полагаю, вы ищете возможность сделать это:

def my_method(arg1)
..
end

def my_method(arg1, arg2)
..
end

Ruby поддерживает это по-другому:

def my_method(*args)
  if args.length == 1
    #method 1
  else
    #method 2
  end
end

Обычным шаблоном также является передача параметров в виде хэша:

def my_method(options)
    if options[:arg1] and options[:arg2]
      #method 2
    elsif options[:arg1]
      #method 1
    end
end

my_method arg1: 'hello', arg2: 'world'

надеюсь, это поможет


15
+1 за то, что многие из нас хотят знать: как работать с переменным числом аргументов в методах Ruby.
ashes999

3
Этот ответ может получить дополнительную информацию о необязательных аргументах. (И, возможно, также назвал аргументы, теперь, когда это - вещь.)
Ajedi32

9

Перегрузка методов имеет смысл в языке со статической типизацией, где можно различать аргументы разных типов

f(1)
f('foo')
f(true)

а также между разным количеством аргументов

f(1)
f(1, 'foo')
f(1, 'foo', true)

Первое различие не существует в рубине. Ruby использует динамическую типизацию или «типизацию утки». Второе различие может быть обработано аргументами по умолчанию или при работе с аргументами:

def f(n, s = 'foo', flux_compensator = true)
   ...
end


def f(*args)
  case args.size
  when  
     ...
  when 2
    ...
  when 3
    ...
  end
end

Это не имеет никакого отношения к строгой типизации. Рубин является строго типизированным, в конце концов.
Йорг Миттаг

8

Это не отвечает на вопрос, почему в ruby ​​нет перегрузки методов, но сторонние библиотеки могут это предоставить.

Библиотека contract.ru позволяет перегружать. Пример адаптирован из учебника:

class Factorial
  include Contracts

  Contract 1 => 1
  def fact(x)
    x
  end

  Contract Num => Num
  def fact(x)
    x * fact(x - 1)
  end
end

# try it out
Factorial.new.fact(5)  # => 120

Обратите внимание, что это на самом деле более мощно, чем перегрузка Java, потому что вы можете указать соответствующие значения (например, 1 ), а не просто типы.

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


1
В реальных приложениях с любым типом ввода-вывода замедление будет только на 0,1-10% (в зависимости от типа ввода-вывода).
Waterlink

1

Я часто делаю следующую структуру:

def method(param)
    case param
    when String
         method_for_String(param)
    when Type1
         method_for_Type1(param)

    ...

    else
         #default implementation
    end
end

Это позволяет пользователю объекта использовать чистый и понятный method_name: метод. Но если он хочет оптимизировать выполнение, он может напрямую вызвать правильный метод.

Кроме того, это делает ваши тесты яснее и лучше.


1

Уже есть отличные ответы на вопрос, почему сторона вопроса. однако, если кто-то ищет другие решения, обратите внимание на функциональную рубиновую жемчужину, которая вдохновлена возможностями сопоставления с образцом Elixir .

 class Foo
   include Functional::PatternMatching

   ## Constructor Over loading
   defn(:initialize) { @name = 'baz' }
   defn(:initialize, _) {|name| @name = name.to_s }

   ## Method Overloading
   defn(:greet, :male) {
     puts "Hello, sir!"
   }

   defn(:greet, :female) {
     puts "Hello, ma'am!"
   }
 end

 foo = Foo.new or Foo.new('Bar')
 foo.greet(:male)   => "Hello, sir!"
 foo.greet(:female) => "Hello, ma'am!"   
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.