Чому не перевантажується методом підтримки рубіну?


146

Замість того, щоб підтримувати метод перевантаження, Ruby замінює існуючі методи. Хтось може пояснити, чому мова була розроблена таким чином?

Відповіді:


166

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

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

Ми не можемо домогтися перевантаження методу, використовуючи перший спосіб, оскільки в рубіні не існує декларації про тип даних ( динамічна набрана мова ). Тож єдиним способом визначити вищевказаний метод є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.

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


22
@ Відповідь Йорга У Міттага, похований далеко внизу, безумовно, варто прочитати.
user2398029

1
І відповідь @Derek Екінса, похований ще далі, пропонує альтернативу
Кирило Дюшон-Доріс

Зауважте майбутнім рубістам ... FWIW ви можете це зробити за допомогою контрактів gem egonschiele.github.io/contracts.ruby/#method-overloading
engineerDave

214

"Перевантаження" - термін, який просто не має сенсу навіть у Рубі. Це в основному є синонімом «статичного аргументу на основі відправки», але Рубін НЕ має статичну відправку взагалі . Отже, причина, чому Ruby не підтримує статичну відправлення на основі аргументів, полягає в тому, що вона не підтримує статичну диспетчеризацію, період. Він не підтримує статичну диспетчеризацію будь-якого виду , на основі аргументів чи іншим чином.

Тепер, якщо ви насправді не конкретно запитуєте про перевантаження, але, можливо, про динамічну розсилку на основі аргументів, то відповідь: тому що Матц не реалізував це. Тому що ніхто більше не намагався її запропонувати. Тому що ніхто інший не намагався його здійснити.

Загалом, динамічне відправлення на основі аргументів мовою з необов'язковими аргументами та списками аргументів змінної довжини дуже важко отримати правильне значення, а ще важче - зробити його зрозумілим. Навіть у мовах зі статичною аргументацією на основі аргументів і без необов'язкових аргументів (наприклад, Java), іноді майже неможливо сказати простому смертному, який буде перевантажено.

У C # ви можете фактично кодувати будь -яку проблему 3-SAT в роздільній здатності перевантаження, а це означає, що роздільна здатність перевантаження в C # є важкою для NP.

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

Є мови, які динамічно розсилаються на основі всіх аргументів процедури, на відміну від об'єктно-орієнтованих мов, які розсилаються лише на "прихованому" нульовому selfаргументі. Наприклад, звичайний Lisp розсилає динамічні типи і навіть динамічні значення всіх аргументів. Clojure посилає на довільну функцію всіх аргументів (що BTW надзвичайно круто і надзвичайно потужно).

Але я не знаю жодної мови ОО з динамічною розсилкою на основі аргументів. Мартін Одерський сказав, що він може розглянути можливість додавання на основі аргументів відправки до Scala, але тільки якщо він може одночасно зняти перевантаження і бути зворотним сумісним як із існуючим кодом Scala, який використовує перевантаження, так і сумісний з Java (він особливо згадував Swing та AWT які грають деякі надзвичайно складні трюки, виконуючи майже кожен неприємний темний кутовий випадок досить складних правил перевантаження Java. Я сам мав кілька ідей щодо додавання на Рубі відправлення на основі аргументів, але я ніколи не міг зрозуміти, як це зробити зворотно сумісним способом.


5
Це правильна відповідь. Прийнята відповідь спрощує. C # DOES назвав параметри та необов'язкові параметри, і все ще реалізує перевантаження, тому це не так просто, як " def method(a, b = true)не буде працювати, тому перевантаження методу неможливо". Це не; це просто важко. Однак я знайшов цю відповідь дуже інформативною.
tandrewnichols

1
@tandrewnichols: просто дати деяке уявлення про те, як "складна" роздільна здатність перевантаження в C # ... можна кодувати будь-яку 3-SAT проблему як роздільну здатність перевантаження в C # і змусити компілятор вирішити її під час компіляції, зробивши таким чином роздільну здатність перевантаження в C # NP -твердий (3-SAT, як відомо, завершено NP). Тепер уявіть, що потрібно робити це не один раз на сайті виклику під час компіляції, а один раз на виклик методу для кожного виклику методу під час виконання.
Jörg W Mittag

1
@ JörgWMittag Чи можете ви включити посилання, що показує кодування проблеми 3-SAT в механізмі вирішення перевантаження?
Кальмар


2
Це не схоже на "перевантаження" є синонімом "статичного відправлення на основі аргументів". Статичний аргумент на основі аргументів - це просто найпоширеніша реалізація перевантаження. Перевантаження - термін реалізації агностичного терміна, що означає "та сама назва методу, але різні реалізації в тій же області".
snovity

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)

Перша відмінність не існує в рубіні. Рубі використовує динамічний набір тексту або "набирання качок". Друге відмінність може бути оброблене за допомогою аргументів за замовчуванням або за допомогою аргументів:

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


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

Це не має нічого спільного з сильним набором тексту. Рубін є строго типізований, в кінці кінців.
Йорг W Міттаг

8

Це не відповідає на питання, чому у ruby ​​немає перевантаження методом, але сторонні бібліотеки можуть його надати.

Бібліотека контрактів.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
У реальних програмах із будь-яким типом IO у вас буде лише 0,1-10% (залежно від того, який тип IO).
Уотерлінк

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.