Dlaczego Ruby nie obsługuje przeciążania metod?


146

Zamiast wspierać przeciążanie metod, Ruby nadpisuje istniejące metody. Czy ktoś może wyjaśnić, dlaczego język został zaprojektowany w ten sposób?

Odpowiedzi:


166

Przeciążenie metod można osiągnąć, deklarując dwie metody o tej samej nazwie i różnych podpisach. Te różne podpisy mogą być:

  1. Argumenty z różnymi typami danych, np .: method(int a, int b) vs method(String a, String b)
  2. Zmienna liczba argumentów, np .: method(a) vs method(a, b)

Nie możemy osiągnąć przeciążenia metod przy użyciu pierwszego sposobu, ponieważ nie ma deklaracji typu danych w ruby ​​( dynamiczny język typizowany ). Zatem jedynym sposobem zdefiniowania powyższej metody jestdef(a,b)

W przypadku drugiej opcji może się wydawać, że możemy osiągnąć przeciążenie metody, ale nie możemy. Powiedzmy, że mam dwie metody z różną liczbą argumentów,

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.

Tak więc ruby ​​musi zachować jedną metodę w łańcuchu wyszukiwania metod o unikalnej nazwie.


22
Odpowiedź @ Jörga W Mittaga, ukryta daleko w dole, jest zdecydowanie warta przeczytania.
user2398029

1
A odpowiedź @Dereka Ekinsa, pogrzebana jeszcze bardziej, stanowi alternatywę
Cyril Duchon-Doris

Uwaga dla przyszłych rubyistów ... FWIW możesz to zrobić za pomocą kontraktów gem egonschiele.github.io/contracts.ruby/#method-overloading
engineerDave

214

„Przeciążanie” to termin, który po prostu nie ma sensu w Rubim. To jest w zasadzie synonimem „statycznego wysłania argumentu opartego na”, ale Ruby nie posiada statyczną wysyłkę w ogóle . Zatem powodem, dla którego Ruby nie obsługuje wysyłania statycznego na podstawie argumentów, jest to, że nie obsługuje on wysyłania statycznego, kropka. Nie obsługuje wysyłania statycznego żadnego rodzaju , opartego na argumentach lub w inny sposób.

Teraz, jeśli tak naprawdę nie pytasz konkretnie o przeciążanie, ale może o dynamiczne wysyłanie oparte na argumentach, odpowiedź brzmi: ponieważ Matz tego nie zaimplementował. Ponieważ nikt inny nie zadał sobie trudu, aby to zaproponować. Ponieważ nikt inny nie zadał sobie trudu, aby go wdrożyć.

Ogólnie rzecz biorąc, dynamiczne rozsyłanie oparte na argumentach w języku z opcjonalnymi argumentami i listami argumentów o zmiennej długości jest bardzo trudne do wykonania, a jeszcze trudniej jest zachować zrozumiałość. Nawet w językach ze statycznym wysyłaniem opartym na argumentach i bez argumentów opcjonalnych (jak na przykład Java), czasami dla zwykłego śmiertelnika prawie niemożliwe jest określenie, które przeciążenie zostanie wybrane.

W C # można faktycznie zakodować dowolny problem 3-SAT w celu rozwiązania przeciążenia, co oznacza, że ​​rozwiązanie przeciążenia w C # jest NP-trudne.

Teraz spróbuj tego z dynamiczną wysyłką, gdzie masz dodatkowy wymiar czasu, który musisz zachować w swojej głowie.

Istnieją języki, które są wysyłane dynamicznie na podstawie wszystkich argumentów procedury, w przeciwieństwie do języków zorientowanych obiektowo, które wysyłają tylko na podstawie „ukrytego” selfargumentu zerowego . Na przykład Common Lisp rozsyła typy dynamiczne, a nawet wartości dynamiczne wszystkich argumentów. Clojure wysyła dowolną funkcję wszystkich argumentów (co przy okazji jest niezwykle fajne i niezwykle potężne).

Ale nie znam żadnego języka OO z dynamiczną dystrybucją opartą na argumentach. Martin Odersky powiedział, że mógłby rozważyć dodanie wysyłania opartego na argumentach do Scali, ale tylko wtedy, gdy może jednocześnie usunąć przeciążenie i być wstecznie kompatybilnym zarówno z istniejącym kodem Scala, który używa przeciążenia, jak i kompatybilnym z Javą (szczególnie wspomniał o Swing i AWT które odgrywają niezwykle złożone sztuczki, ćwicząc prawie każdy paskudny ciemny kąt raczej skomplikowanych reguł przeciążania Javy). Sam miałem kilka pomysłów na dodanie wysyłania opartego na argumentach do Rubiego, ale nigdy nie mogłem wymyślić, jak to zrobić w sposób zgodny wstecz.


5
To jest właściwa odpowiedź. Przyjęta odpowiedź zbytnio upraszcza. C # ma nazwane parametry i parametry opcjonalne i nadal implementuje przeciążanie, więc nie jest tak proste, jak „ def method(a, b = true)nie zadziała, dlatego przeciążanie metod jest niemożliwe”. To nie jest; to jest po prostu trudne. Uważam jednak, że TA odpowiedź jest naprawdę pouczająca.
tandrewnichols

1
@tandrewnichols: tylko po to, aby dać wyobrażenie, jak „trudne” jest rozwiązanie przeciążenia w C #… możliwe jest zakodowanie dowolnego problemu 3-SAT jako rozwiązanie przeciążenia w C # i zlecenie kompilatorowi rozwiązania go w czasie kompilacji, dzięki czemu rozwiązanie przeciążenia w C # NP -twardy (wiadomo, że 3-SAT jest NP-zupełny). Teraz wyobraź sobie, że musisz to robić nie raz na wywołanie witryny w czasie kompilacji, ale raz na wywołanie metody dla każdego wywołania metody w czasie wykonywania.
Jörg W Mittag,

1
@ JörgWMittag Czy możesz dołączyć łącze pokazujące kodowanie problemu 3-SAT w mechanizmie rozwiązywania problemu?
Squidly


2
Nie wygląda na to, że „przeciążanie” jest synonimem „statycznego wysyłania na podstawie argumentów”. Statyczne wysyłanie oparte na argumentach jest po prostu najpowszechniejszą implementacją przeciążenia. Przeciążanie to termin niezależny od implementacji, oznaczający „tę samą nazwę metody, ale różne implementacje w tym samym zakresie”.
snovity

85

Zakładam, że szukasz możliwości zrobienia tego:

def my_method(arg1)
..
end

def my_method(arg1, arg2)
..
end

Ruby obsługuje to w inny sposób:

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

Typowym wzorcem jest również przekazywanie opcji jako hash:

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'

Mam nadzieję, że to pomoże


15
+1 za dostarczenie tego, co wielu z nas po prostu chce wiedzieć: jak pracować ze zmienną liczbą argumentów w metodach Rubiego.
popioły 999

3
W tej odpowiedzi mogą być przydatne dodatkowe informacje o argumentach opcjonalnych. (I być może także nazwane argumenty, teraz, gdy to jest rzecz.)
Ajedi32,

9

Przeciążanie metod ma sens w języku z typowaniem statycznym, w którym można rozróżnić różne typy argumentów

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

a także między różnymi liczbami argumentów

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

Pierwsze rozróżnienie nie istnieje w rubinie. Ruby używa dynamicznego pisania lub "kaczego pisania". Drugie rozróżnienie można obsłużyć domyślnymi argumentami lub pracując z argumentami:

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


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

Nie ma to nic wspólnego z silnym pisaniem. W końcu Ruby jest mocno typowany.
Jörg W Mittag,

8

To nie odpowiada na pytanie, dlaczego Ruby nie ma przeciążania metod, ale biblioteki innych firm mogą to zapewnić.

Contracts.ruby biblioteka umożliwia przeciążanie. Przykład zaczerpnięty z samouczka:

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

Zauważ, że jest to w rzeczywistości potężniejsze niż przeciążanie Javy, ponieważ możesz określić wartości do dopasowania (np. 1), A nie tylko typy.

Zauważysz jednak zmniejszoną wydajność przy użyciu tego; będziesz musiał przeprowadzić testy porównawcze, aby zdecydować, ile możesz tolerować.


1
W rzeczywistych aplikacjach z dowolnym rodzajem IO będziesz miał tylko 0,1-10% (w zależności od rodzaju IO) spowolnienia.
Waterlink

1

Często wykonuję następującą strukturę:

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

    ...

    else
         #default implementation
    end
end

Pozwala to użytkownikowi obiektu na użycie czystej i przejrzystej metody nazwa_metody: metoda Jeśli jednak chce zoptymalizować wykonanie, może bezpośrednio wywołać właściwą metodę.

Ponadto sprawia, że ​​test jest bardziej przejrzysty i lepszy.


1

są już świetne odpowiedzi na pytanie dlaczego. Jeśli jednak ktoś szuka innych rozwiązań, sprawdź funkcjonalno-rubinową perełkę zainspirowaną dopasowaniem wzorów 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!"   
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.