Почему ruby ​​не поддерживает перегрузку методов? - PullRequest
138 голосов
/ 21 февраля 2012

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

Ответы [ 7 ]

200 голосов
/ 21 февраля 2012

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

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

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

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

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

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

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

152 голосов
/ 21 февраля 2012

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

  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 ​​должен поддерживать один метод в цепочке поиска методов с уникальным именем.

82 голосов
/ 21 февраля 2012

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

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'

Надеюсь, что поможет

9 голосов
/ 21 февраля 2012

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

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 голосов
/ 19 марта 2015

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

Библиотека Contract.ruby допускает перегрузку.Пример, адаптированный из учебного пособия:

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 голос
/ 06 января 2019

уже есть отличные ответы о том, почему сторона вопроса. однако, если кто-то ищет другие решения, обратите внимание на functions-rub y gem, который вдохновлен 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!"   
1 голос
/ 26 мая 2013

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

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

    ...

    else
         #default implementation
    end
end

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...