İşlev aşırı yüklenmesi? Evet veya hayır [kapalı]


17

Statik ve kuvvetle yazılan, derlenmiş bir dil geliştiriyorum ve işlev aşırı yüklemesini bir dil özelliği olarak dahil edip etmeme fikrine yeniden bakıyorum. Biraz önyargılı olduğumu fark ettim C[++|#].

En inandırıcı argümanlar nelerdir için ve karşı bir dilde aşırı fonksiyonu dahil?


DÜZENLEME: Karşıt görüşü olmayan kimse yok mu?

Bertrand Meyer (1985/1986'da Eiffel'in yaratıcısı), aşırı yükleme yöntemini çağırıyor: (kaynak)

OO dilinin anlamsal gücüne hiçbir şey getirmeyen, ancak okunabilirliği engelleyen ve herkesin görevini zorlaştıran bir makyaj mekanizması

Şimdi bunlar bazı kapsamlı genellemeler, ama akıllı bir adam, bu yüzden gerektiğinde onları destekleyebileceğini söylemek güvenli. Aslında, neredeyse Brad Abrams (CLSv1 geliştiricilerinden biri) .NET'in yöntem aşırı yüklemesini desteklememesi gerektiğine ikna etti. (kaynak) Bu bazı güçlü şeyler. Herkes düşüncelerine biraz ışık tutabilir mi ve bakış açısının 25 yıl sonra hala haklı olup olmadığını?

Yanıtlar:


24

İşlev aşırı yüklenmesi, C ++ stili şablon kodu için kesinlikle önemlidir. Farklı türler için farklı işlev adları kullanmam gerekiyorsa, genel kod yazamıyorum. Bu, C ++ kitaplığının büyük ve yoğun olarak kullanılan bir bölümünü ve C ++ 'ın işlevselliğinin çoğunu ortadan kaldıracaktır.

Genellikle üye işlev adlarında bulunur. A.foo()tamamen farklı bir işlev çağırabilir B.foo(), ancak her iki işlev de adlandırılır foo. Operatörlerde mevcut+Tamsayılara ve kayan nokta sayılarına uygulandığında farklı şeyler yaptığı ve genellikle bir dize birleştirme operatörü olarak kullanılır. Düzenli fonksiyonlarda da izin vermemek tuhaf görünüyor.

Tam olarak adlandırılan işlevin iki veri türüne bağlı olduğu Ortak Lisp tarzı "multimethods" kullanımını sağlar. Ortak Lisp Nesne Sisteminde programlamadıysanız, bunu işe yaramaz olarak adlandırmadan önce deneyin. C ++ akışları için çok önemlidir.

İşlev aşırı yüklemesi olmayan G / Ç (veya daha kötü olan varadik işlevler), farklı türdeki değerleri yazdırmak veya farklı türdeki değerleri ortak bir türe (Dize gibi) dönüştürmek için bir dizi farklı işlev gerektirir.

İşlev aşırı yüklenmesi olmadan, bazı değişkenlerin veya değerlerin türünü değiştirirsem, onu kullanan her işlevi değiştirmem gerekir. Yeniden düzenleme kodunu zorlaştırır.

Kullanıcının hangi tür adlandırma kuralının kullanımda olduğunu hatırlaması gerekmediğinde API'leri kullanmayı kolaylaştırır ve kullanıcı yalnızca standart işlev adlarını hatırlayabilir.

Operatör aşırı yüklemesi olmadan, bu temel işlem birden fazla tipte kullanılabiliyorsa, her işlevi kullandığı türlerle etiketlememiz gerekir. Bu aslında Macar gösterimidir, bunu yapmanın kötü yolu.

Genel olarak, bir dili çok daha kullanışlı hale getirir.


1
+1, hepsi çok iyi puan. Ve bana güven, multimethods'un işe yaramaz olduğunu düşünmüyorum ... Ziyaretçi desenini her kullanmaya zorlandığımda yazdığım klavyeyi lanetliyorum.
Kendine not - bir isim düşün

Bu adam işlevi geçersiz kılma ve aşırı yüklemeyi tanımlamıyor mu?
dragosb

8

En azından Haskell'deki tür sınıflarının farkında olmanızı tavsiye ederim. Tür sınıfları, operatörün aşırı yüklenmesine disiplinli bir yaklaşım olarak yaratıldı, ancak başka kullanımlar buldular ve bir dereceye kadar Haskell'i bu hale getirdi.

Örneğin, geçici aşırı yüklemeye bir örnek (tam olarak geçerli Haskell değil):

(==) :: Int -> Int -> Bool
x == y = ...
x /= y = not (x == y)

(==) :: Char -> Char -> Bool
x == y = ...
x /= y = not (x == y)

Ve işte sınıf sınıflarıyla aynı aşırı yükleme örneği:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

    x /= y  =  not (x == y)

instance Eq Int where
    x == y  = ...

instance Eq Char where
    x == y  = ...

Bunun bir dezavantajı tüm typeclasses için korkak isimlerle gelmek zorunda olduğunu (Haskell olduğu gibi, oldukça soyut olması Monad, Functor, Applicativehem de daha basit ve daha tanınabilir Eq, Numve Ord).

Bir tersi, bir tip sınıfına aşina olduğunuzda, o sınıftaki herhangi bir türü nasıl kullanacağınızı biliyorsunuzdur. Ayrıca, işlevleri aşağıdaki gibi gerekli sınıfları uygulamayan türlerden korumak kolaydır:

group :: (Eq a) => [a] -> [[a]]
group = groupBy (==)

Düzenleme: Haskell'de, ==iki farklı türü kabul eden bir işleç istiyorsanız, çok parametreli bir sınıf kullanabilirsiniz:

class Eq a b where
    (==) :: a -> b -> Bool
    (/=) :: a -> b -> Bool

    x /= y  =  not (x == y)

instance Eq Int Int where
    x == y  = ...

instance Eq Char Char where
    x == y  = ...

instance Eq Int Float where
    x == y  = ...

Tabii ki, bu muhtemelen kötü bir fikir, çünkü elma ve portakalları açıkça karşılaştırmanıza izin veriyor. Ancak, bu düşünebilirsiniz +bir ekleme beri Word8bir etmek Intgerçekten bazı bağlamlarda yapılacak mantıklı şeydir.


+1, bu kavramı ilk okuduğumdan beri kavramaya çalışıyorum ve bu yardımcı oluyor. Bu paradigma, örneğin (==) :: Int -> Float -> Boolherhangi bir yerde tanımlamayı imkansız hale getiriyor mu? (elbette iyi bir fikir olup olmadığına bakılmaksızın)
Kendine not - bir ad düşünün

Çok parametreli tip sınıflarına izin verirseniz (Haskell'in bir uzantı olarak desteklediği) yapabilirsiniz. Cevabı bir örnekle güncelledim.
Joey Adams

Hmm, ilginç. Yani, temel olarak, class Eq a ...sözde C-ailesine çevrilecekti interface Eq<A> {bool operator==(A x, A y);}ve keyfi nesneleri karşılaştırmak için şablon kod kullanmak yerine bu 'arabirimi' kullanıyorsunuz. Bu doğru mu?
Kendine not -

Sağ. Ayrıca, Go'daki arayüzlere hızlı bir şekilde göz atmak isteyebilirsiniz. Yani, bir arabirimi uygulamak olarak bir tür bildirmek zorunda değilsiniz, o arabirim için tüm yöntemleri uygulamak zorundasınız.
Joey Adams

1
@ Notetoself-thinkofaname: Ek işleçlerle ilgili olarak - evet ve hayır. Çalışabilmesine imkan ==farklı bir ad-uzayda olmak ama bunu geçersiz kılmak için izin vermez. PreludeVarsayılan olarak tek bir ad alanının ( ) bulunduğunu, ancak uzantıları kullanarak veya açıkça içe aktararak yüklemeyi engelleyebileceğinizi lütfen unutmayın ( geçerli ad alanına import Prelude ()hiçbir şey aktarmaz Preludeve import qualified Prelude as Psembol eklemez).
Maciej Piechotka

4

Fonksiyon aşırı yüklenmesine izin verin, aşağıdakileri isteğe bağlı parametrelerle yapamazsınız (ya da hoşunuza gidemiyorsa).

önemsiz örnek yöntem kabul etmiyorbase.ToString()

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...

İyi yazılmış bir dil için, evet. Zayıf yazılmış bir dil için hayır. Yukarıdakileri, zayıf yazılan bir dilde yalnızca bir işlevle yapabilirsiniz.
spex

2

Ben her zaman fonksiyon aşırı yükleme yerine varsayılan parametreleri tercih ettik. Aşırı yüklenmiş işlevler genellikle varsayılan parametrelerle "varsayılan" sürümü çağırır. Neden yazmalı

int indexOf(char ch)
{
  return self.indexOf(ch, 0);
}

int indexOf(char ch, int fromIndex)
{
  // Do whatever
}

Ne zaman yapabilirim:

int indexOf(char ch, int fromIndex=0)
{
  // Do whatever
}

Bununla birlikte, bazen aşırı yüklenmiş işlevlerin, varsayılan parametrelerle başka bir varyantı çağırmak yerine farklı şeyler yaptığını anlıyorum ... ancak böyle bir durumda, sadece bir vermek kötü bir fikir değil (aslında, muhtemelen iyi bir fikir) farklı isim.

(Ayrıca, Python tarzı anahtar kelime bağımsız değişkenleri varsayılan parametrelerle gerçekten iyi çalışır.)


Tamam, tekrar deneyelim ve bu sefer mantıklı olmaya çalışacağım ... Peki Array Slice(int start, int length) {...}aşırı yüklenmeye ne dersin Array Slice(int start) {return this.Slice(start, this.Count - start);}? Bu, varsayılan parametreler kullanılarak kodlanamaz. Sizce farklı isimler verilmeli mi? Öyleyse, bunları nasıl adlandırırsınız?
Kendine not -

Bu, aşırı yüklemenin tüm kullanımlarını ele almaz.
MetalMikester

@MetalMikester: Kaçırdığım şeyleri ne düşünüyorsunuz?
mipadi

@mipadi Listede indexOf(char ch)+ gibi şeyleri özlüyor indexOf(Date dt). Ben de varsayılan değerleri seviyorum ama statik yazarak değiştirilebilir değildir.
Mark

2

Az önce Java'yı tanımladınız. Veya C #.

Neden tekerleği yeniden icat ediyorsun?

Dönüş türünün yöntem imzasının bir parçası olduğundan ve kalplerinizin içeriğine aşırı yüklendiğinden emin olun, söylemek zorunda olmadığınızda kodu gerçekten temizler.

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }

7
Dönüş türünün, farkında olduğum herhangi bir dilde yöntem imzasının bir parçası olmamasının veya en azından aşırı yük çözünürlüğü için kullanılabilecek bir parçanın olmamasının bir nedeni vardır. Bir işlevi yordam olarak çağırdığınızda, sonucu bir değişkene veya özelliğe atamadan, derleyicinin tüm diğer bağımsız değişkenler aynıysa hangi sürümü çağırması gerektiğini nasıl anlaması gerekir?
Mason Wheeler

@Mason: Beklenmesi gereken dönüş türüne göre beklenen dönüş türünü sezgisel olarak tespit edebilirsiniz, ancak bunun yapılmasını beklemiyorum.
Josh K

1
Hmm ... buluşsal yönteminiz herhangi bir geri dönüş değeri beklemediğinizde neyin geri dönmesi beklendiğini nasıl biliyor?
Mason Wheeler

1
Tip-beklentisi sezgisel olarak aslında yerinde ... gibi şeyler yapabilirsiniz EnumX.Flag1 | Flag2 | Flag3. Yine de bunu uygulamayacağım. Yaptım ve dönüş türü kullanılmadı, ben bir dönüş türü için bakmak istiyorum void.
Kendine not - bir isim düşün

1
@Mason: Bu iyi bir soru, ancak bu durumda geçersiz bir işlev ararım (belirtildiği gibi). Ayrıca teoride bunlardan herhangi birini seçebilirsiniz, çünkü hepsi aynı işlevi yerine getirecek, verileri farklı bir biçimde döndürecektir.
Josh K

2

Grrr .. henüz yorum yapmak için yeterli ayrıcalık yok ..

@Mason Wheeler: Dönüş türüne aşırı yük bindiren Ada'nın farkında olun. Ayrıca benim dilim Felix de bazı bağlamlarda, özellikle bir işlev başka bir işlev döndürdüğünde ve böyle bir çağrı olduğunda:

f a b  // application is left assoc: (f a) b

b tipi aşırı yük çözünürlüğü için kullanılabilir. Ayrıca bazı durumlarda dönüş tipinde C ++ aşırı yüklenmeleri:

int (*f)(int) = g; // choses g based on type, not just signature

Aslında, tür çıkarımını kullanarak dönüş türüne aşırı yükleme algoritmaları vardır. Aslında bir makine ile yapmak o kadar da zor değil, sorun insanların bunu zor bulması. (Sanırım anahat Ejderha Kitabında verilmiştir, doğru hatırlarsam algoritmaya see-saw algoritması denir)


2

İşlev aşırı yüklenmesine karşı kullanım örneği: Aynı şeyleri yapan ancak çok çeşitli kalıplarda tamamen farklı argüman kümeleri ile 25 benzer adlı yöntem.

İşlev aşırı yüklemesi uygulamamaya karşı kullanım durumu: Aynı desende çok benzer tür kümeleri ile benzer şekilde adlandırılmış 5 yöntem.

Günün sonunda, her iki durumda da üretilen bir API için belgeleri okumak için sabırsızlanıyorum.

Ancak bir durumda, kullanıcıların neler yapabileceği ile ilgilidir. Diğer durumda, kullanıcıların bir dil kısıtlaması nedeniyle yapması gerekenler budur. IMO, en azından program yazarlarının belirsizlik yaratmadan makul bir şekilde aşırı yüklenebilecek kadar akıllı olmalarına izin vermek daha iyidir. Ellerini tokatlayıp seçeneği kaldırdığınızda, temelde belirsizliğin olacağını garanti edersiniz. Kullanıcıya her zaman yanlış şeyi yapacaklarını varsaymaktan ziyade doğru şeyi yapmak için güvenmekle ilgiliyim. Deneyimlerime göre, korumacılık bir dilin toplumunda daha da kötü davranışlara yol açma eğilimindedir.


1

Benim dilim Felix sıradan aşırı yükleme ve çok tip tipi sınıfları sağlamayı seçti.

Özellikle aşırı sayıda sayısal tip olan bir dilde (Felix'in tüm C'nin sayısal tiplerine sahip olduğu) bir dilde (açık) aşırı yüklemeyi gerekli görüyorum. Bununla birlikte, şablonları bağımlı hale getirerek aşırı yüklemeyi kötüye kullanan C ++ 'dan farklı olarak, Felix polimorfizmi parametriktir: C ++' daki şablonlar kötü tasarlanmış olduğundan C ++ 'daki şablonlar için aşırı yüklemeye ihtiyacınız vardır.

Tür sınıfları da Felix'te verilmektedir. C ++ bilen ancak Haskell'i bilmeyenler için, aşırı yükleme olarak tanımlayanları görmezden gelin. Uzaktan aşırı yükleme gibi değildir, bunun yerine şablon uzmanlığı gibidir: uygulamadığınız bir şablon bildirir, ardından ihtiyacınız olan belirli durumlar için uygulamalar sağlarsınız. Yazım parametrik olarak polimorfiktir, uygulama geçici örnekleme yoluyladır, ancak kısıtlayıcı olması amaçlanmamıştır: amaçlanan semantiği uygulamak zorundadır.

Haskell'de (ve C ++) anlambilimi ifade edemezsiniz. C ++ 'da "Kavramlar" fikri kabaca anlambilime yaklaşma girişimidir. Felix'te niyeti aksiyomlar, indirimler, lemmalar ve teoremlerle yaklaşık olarak tahmin edebilirsiniz.

Felix gibi iyi ilkeli bir dilde (açık) aşırı yüklemenin ana ve tek avantajı, hem program yazarı hem de kod inceleyici için kütüphane işlev adlarını hatırlamayı kolaylaştırmasıdır.

Aşırı yüklenmenin birincil dezavantajı, onu uygulamak için gereken karmaşık algoritmadır. Ayrıca, tür çıkarımıyla da pek iyi oturmuyor: ikisi tamamen özel olmasa da, her ikisini de yapan algoritma programcının muhtemelen sonuçları tahmin edemeyeceği kadar karmaşıktır.

C ++ 'da özensiz bir eşleme algoritmasına sahip olduğu ve aynı zamanda otomatik tip dönüşümleri desteklediği için bu da bir sorundur: Felix'te tam bir eşleşme ve otomatik tip dönüşümü gerektirmeyerek bu sorunu "düzelttim".

Sanırım bir seçeneğiniz var: aşırı yükleme veya tip çıkarım. Çıkarım şirin, ancak çatışmaları doğru bir şekilde teşhis edecek şekilde uygulamak da çok zor. Örneğin Ocaml, bir çatışmayı nerede saptadığını, ancak beklenen türü nereden çıkardığını söylemez.

Aşırı yükleme çok daha iyi değil, tüm adayları anlatmaya çalışan kaliteli bir derleyiciniz olsa bile, adayların polimorfik olup olmadığını okumak zor olabilir ve hatta C ++ şablon hackery ise daha da kötü olabilir.


Kulağa ilginç geliyor. Daha fazlasını okumak isterim, ancak Felix web sayfasındaki dokümanlara bağlantı koptu.
Kendine not -

Evet, tüm site şu anda yapım aşamasındadır (tekrar), üzgünüm.
Yttrill

0

Bağlam haline gelir, ancak bence aşırı yükleme, bir başkası tarafından yazılmış bir sınıfı kullanırken bir sınıfı çok daha kullanışlı hale getirir. Genellikle daha az fazlalık ile sonuçlanırsınız.


0

C-ailesi dillerine aşina olan kullanıcılara sahip olmak istiyorsanız, evet, yapmalısınız çünkü kullanıcılarınız bunu bekliyor olacak.

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.