Ruby yöntem aşırı yüklemesini neden desteklemiyor?


146

Ruby, metodun aşırı yüklenmesini desteklemek yerine mevcut metotların üzerine yazar. Dilin neden bu şekilde tasarlandığını kimse açıklayabilir mi?

Yanıtlar:


167

Metod aşırı yüklemesi, aynı isim ve farklı imzalara sahip iki metot bildirilerek gerçekleştirilebilir. Bu farklı imzalar şunlar olabilir:

  1. Farklı veri türlerine sahip bağımsız değişkenler, örneğin: method(int a, int b) vs method(String a, String b)
  2. Değişken sayıda bağımsız değişken, örneğin: method(a) vs method(a, b)

Ruby'de ( dinamik tipli dil ) veri türü bildirimi olmadığı için birinci yolu kullanarak yöntem aşırı yüklemesini başaramayız . Dolayısıyla, yukarıdaki yöntemi tanımlamanın tek yoludef(a,b)

İkinci seçenekle, yöntem aşırı yüklemesini başarabilirmişiz gibi görünebilir, ancak yapamayız. Diyelim ki farklı sayıda argümana sahip iki yöntemim var,

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.

Bu nedenle Ruby'nin, benzersiz bir ada sahip yöntem arama zincirinde bir yöntemi sürdürmesi gerekir.


23
@ Jörg W Mittag'ın çok aşağıda gömülü cevabı kesinlikle okumaya değer.
user2398029

1
Ve @Derek Ekins'in daha da derinlere gömülen cevabı bir alternatif sunuyor
Cyril Duchon-Doris

Gelecekteki rubyistlere not ... FWIW bunu sözleşmelerle yapabilirsiniz gem egonschiele.github.io/contracts.ruby/#method-overloading
engineeringDave

216

"Aşırı yükleme", Ruby'de hiç mantıklı olmayan bir terimdir. Temelde "statik argüman tabanlı sevk" ile eşanlamlı olmakla Yakut gelmez sahip statik sevk hiç . Bu nedenle, Ruby'nin argümanlara dayalı statik gönderimi desteklememesinin nedeni, statik dağıtımı (nokta) desteklememesidir. Bağımsız değişken tabanlı veya başka türlü statik gönderimi desteklemez .

Şimdi, eğer değil aslında özellikle aşırı soran, ama belki yaklaşık dinamik argüman tabanlı sevk, daha sonra cevap: Matz bunu uygulamaya yoktu çünkü. Çünkü kimse bunu teklif etmeye zahmet etmedi. Çünkü kimse onu uygulamaya zahmet etmedi.

Genel olarak, isteğe bağlı argümanlar ve değişken uzunluklu argüman listeleri içeren bir dilde dinamik argüman tabanlı gönderim, doğru yapmak çok zordur ve anlaşılır tutmak daha da zordur . Statik argüman tabanlı gönderimi olan ve isteğe bağlı argümanlar içermeyen dillerde bile (örneğin Java gibi), bazen bir ölümlü için hangi aşırı yükün seçileceğini söylemek neredeyse imkansızdır .

C # 'da, aslında herhangi bir 3-SAT problemini aşırı yük çözünürlüğüne kodlayabilirsiniz , bu da C #' daki aşırı yük çözünürlüğünün NP-zor olduğu anlamına gelir.

Şimdi bunu , aklınızda tutmanız gereken ek zaman boyutuna sahip olduğunuz dinamik gönderim ile deneyin .

Yalnızca "gizli" sıfırıncı selfargüman üzerinden gönderilen nesne yönelimli dillerin aksine, bir prosedürün tüm argümanlarına dayalı olarak dinamik olarak gönderilen diller vardır . Örneğin Common Lisp, tüm argümanların dinamik türlerini ve hatta dinamik değerlerini gönderir. Clojure, tüm argümanların keyfi bir işlevini gönderir (BTW son derece havalı ve son derece güçlüdür).

Ancak dinamik argüman tabanlı gönderime sahip herhangi bir OO dili bilmiyorum. Martin Odersky o söyledi olabilir ama, Scala için argüman tabanlı sevk eklemeyi düşünün sadece o aynı zamanda aşırı kaldırabilirsiniz eğer ve hem kullanımları aşırı ve Java uyumlu olduğunu Scala mevcut kodla geriye doğru uyumlu olması (o özellikle salıncak ve AWT söz Java'nın oldukça karmaşık aşırı yükleme kurallarının hemen hemen her kötü karanlık köşesini uygulayan son derece karmaşık hileler oynar). Ruby'ye argümana dayalı gönderi eklemekle ilgili bazı fikirlerim vardı, ancak bunu geriye dönük olarak nasıl yapacağımı asla çözemedim.


5
Bu doğru cevap. Kabul edilen cevap fazlasıyla basitleştiriyor. C # DOES, adlandırılmış parametrelere ve isteğe bağlı parametrelere sahiptir ve hala aşırı yükleme uygular, bu nedenle " def method(a, b = true)çalışmayacak, bu nedenle yöntem aşırı yüklemesi imkansız " kadar basit değildir. Değil; bu sadece zor. Ancak BU cevabı gerçekten bilgilendirici buldum.
tandrewnichols

1
@tandrewnichols: C # 'da aşırı yük çözümlemesinin ne kadar "zor" olduğu konusunda biraz fikir vermek için… herhangi bir 3-SAT problemini C #' ta aşırı yük çözümü olarak kodlamak ve derleyicinin bunu derleme zamanında çözmesini sağlamak, böylece C # NP'de aşırı yük çözümü yapmak mümkündür. -hert (3-SAT'ın NP-tamamlandığı bilinmektedir). Şimdi bunu derleme zamanında arama sitesi başına bir kez değil , çalışma zamanında her bir yöntem çağrısı için yöntem başına bir kez yapmak zorunda olduğunuzu hayal edin .
Jörg W Mittag

1
@ JörgWMittag Aşırı yük çözme mekanizmasında 3-SAT probleminin kodlamasını gösteren bir bağlantı ekleyebilir misiniz?
Squidly


2
"Aşırı yükleme", "statik bağımsız değişken tabanlı gönderim" ile eşanlamlı gibi görünmüyor. Statik argüman tabanlı gönderim, aşırı yüklemenin en yaygın uygulamasıdır. Aşırı yükleme, "aynı yöntem adı, ancak aynı kapsamdaki farklı uygulamalar" anlamına gelen uygulamadan bağımsız bir terimdir.
snovity

85

Bunu yapma yeteneğini aradığınızı varsayıyorum:

def my_method(arg1)
..
end

def my_method(arg1, arg2)
..
end

Ruby bunu farklı bir şekilde destekler:

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

Ortak bir model de seçenekleri bir hash olarak geçirmektir:

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'

umarım yardımcı olur


15
Birçoğumuzun bilmek istediği şeyi sağlamak için +1: Ruby yöntemlerinde değişken sayıda argümanla nasıl çalışılır.
ashes999

3
Bu cevap, isteğe bağlı argümanlar hakkında ek bilgilerden yararlanabilir. (Ve belki argümanlar da adlandırıldı, şimdi bunlar bir şey.)
Ajedi32

9

Metot aşırı yükleme, farklı argüman türleri arasında ayrım yapabileceğiniz statik yazmanın olduğu bir dilde anlamlıdır

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

yanı sıra farklı sayıda argüman arasında

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

İlk ayrım yakutta yoktur. Ruby dinamik yazma veya "ördek yazma" kullanır. İkinci ayrım, varsayılan bağımsız değişkenlerle veya bağımsız değişkenlerle çalışılarak ele alınabilir:

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


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

Bunun güçlü yazımla ilgisi yok. Yakut edilir şiddetle sonuçta yazdınız.
Jörg W Mittag

8

Bu, Ruby'nin neden aşırı yöntem yüklemesine sahip olmadığı sorusuna yanıt vermez, ancak üçüncü taraf kitaplıklar bunu sağlayabilir.

Contracts.ruby kütüphanesi aşırı yükleme sağlar. Öğreticiden uyarlanmış örnek:

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

Bunun aslında Java'nın aşırı yüklemesinden daha güçlü olduğunu unutmayın, çünkü eşleşecek değerleri belirtebilirsiniz (örn. 1 yalnızca türleri değil, ) .

Yine de bunu kullanarak düşük performans göreceksiniz; Ne kadarını tolere edebileceğinize karar vermek için kıyaslamalar yapmanız gerekecektir.


1
Herhangi bir IO içeren gerçek dünya uygulamalarında, sadece% 0.1-10 (ne tür bir IO'ya bağlı olarak) yavaşlama yaşayacaksınız.
Waterlink

1

Sık sık aşağıdaki yapıyı yaparım:

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

    ...

    else
         #default implementation
    end
end

Bu, nesnenin kullanıcısının clean and clear method_name: method kullanmasına izin verir. Ancak, yürütmeyi optimize etmek isterse, doğrudan doğru yöntemi çağırabilir.

Ayrıca, testinizi daha net ve daha iyi hale getirir.


1

sorunun neden tarafında zaten harika cevaplar var. ancak, başka çözümler arayan biri Elixir desen eşleştirme özelliklerinden esinlenen işlevsel yakut mücevherini kontrol edin .

 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!"   
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.