Yöntem aşırı yükleme sözdizimsel şekerden başka bir şey midir? [kapalı]


19

Yöntem aşırı yüklenme bir tür polimorfizm midir? Bana göre, aynı isimde ve farklı parametrelerde yöntemlerin farklılaşması gibi görünüyor. Yani stuff(Thing t)ve stuff(Thing t, int n)uzak derleyici olarak tamamen farklı yöntemler ve çalışma zamanı ilgileniyoruz.

Arayan tarafında, farklı nesne türlerine - polimorfizm - farklı davranan aynı yöntem olduğu yanılsamasını yaratır. Ama bu sadece bir yanılsama, çünkü aslında stuff(Thing t)ve stuff(Thing t, int n)tamamen farklı yöntemler.

Yöntem aşırı yükleme sözdizimsel şekerden başka bir şey midir? Bir şey mi kaçırıyorum?


Sözdizimsel şeker için yaygın bir tanım, tamamen lokal olmasıdır . Bir kod parçasını 'tatlandırılmış' eşdeğerine değiştirmek veya tam tersi, programın genel yapısını etkilemeyen yerel değişiklikleri içerir. Ve bence yöntem aşırı yüklemesi bu kritere tam olarak uyuyor. Göstermek için bir örneğe bakalım:

Bir sınıf düşünün:

class Reader {
    public String read(Book b){
        // .. translate the book to text
    }
    public String read(File b){
        // .. translate the file to text
    }
}

Şimdi bu sınıfı kullanan başka bir sınıf düşünün:

/* might not be the best example */
class FileProcessor {
    Reader reader = new Reader();
    public void process(File file){
        String text = reader.read(file);
        // .. do stuff with the text
    }
}

Tamam. Şimdi, aşırı yöntem yükünü normal yöntemlerle değiştirirsek neyin değişmesi gerektiğini görelim:

readYöntemleri Readeriçin değişim readBook(Book)ve readFile(file). Sadece isimlerini değiştirme meselesi.

Arama kodu FileProcessorbiraz reader.read(file)değişir : olarak değişir reader.readFile(file).

Ve bu kadar.

Gördüğünüz gibi, yöntem aşırı yüklemesi ile kullanmamak arasındaki fark tamamen yereldir . Bu yüzden saf sözdizimsel şeker olarak nitelendirildiğini düşünüyorum.

Eğer varsa, itirazlarını duymak isterim, belki bir şey eksik.


48
Sonunda, herhangi bir programlama dili özelliği ham montajcı için sadece sözdizimsel şekerdir.
Philipp

31
@Philipp: Üzgünüm, ama bu gerçekten aptalca bir ifade. Programlama dilleri kullanışlılıklarını sözdiziminden değil anlambilimden alır. Tip sistemi gibi özellikler, aslında daha fazla yazmanızı gerektirse de size gerçek garantiler verir .
back2dos

3
Kendinize şunu sorun: Aşırı yükleme operatörü sadece sözdizimsel şeker mi? Doğru tuttuğunuz bu soruya cevap ne olursa olsun, sorduğunuz sorunun cevabıdır;)
back2dos

5
@ back2dos: Tamamen katılıyorum. "Her şey sadece montajcı için sözdizimsel şekerdir" cümlesini çok sık okuyorum ve açıkça yanlış. Sözdizimsel şeker, mevcut herhangi bir sözdizimi için yeni bir anlambilim eklemeyen alternatif (muhtemelen daha güzel) bir sözdizimidir.
Giorgio

6
@Giorgio: doğru! Matthias Felleisen'in ifadeyle ilgili dönüm noktası makalesinde kesin bir tanım var. Temel olarak: sözdizimsel şeker tamamen lokaldir. Dil özelliğinin kullanımını kaldırmak için programın genel yapısını değiştirmeniz gerekiyorsa, sözdizimsel şeker değildir. Assembler polimorfik OO FEC Yani genellikle küresel sevk mantığı ekleyerek içerir değil bu nedenle OO taşmamıştır değil "Çevirici için sadece sözdizimsel şeker".
Jörg W Mittag

Yanıtlar:


29

Bunu cevaplamak için önce "sözdizimsel şeker" tanımına ihtiyacınız vardır. Vikipedi ile gideceğim :

Bilgisayar biliminde sözdizimsel şeker, bir şeyleri okumayı veya ifade etmeyi kolaylaştırmak için tasarlanmış bir programlama dilinde sözdizimidir. Dili insan kullanımı için "daha tatlı" yapar: her şey daha açık, daha özlü veya bazılarının tercih edebileceği alternatif bir tarzda ifade edilebilir.

[...]

Özellikle, bir dilde bir yapıya, dilin neler yapabileceği üzerinde herhangi bir etkisi olmadan dilden çıkarılabiliyorsa sözdizimsel şeker denir.

Yani, bu tanım altında, Java'nın varargs veya Scala'nın kavrayışı gibi özellikler sözdizimsel şekerdir: altta yatan dil özelliklerine (ilk durumda bir dizi, ikincisinde harita / flatmap / filtreye çağrılar) dönüşürler ve bunları kaldırırlar. dil ile yapabileceğiniz şeyleri değiştirmeyin.

Bununla birlikte, yöntem aşırı yüklenmesi, bu tanım altında sözdizimsel şeker değildir, çünkü onu kaldırmak dili temelden değiştirir (artık argümanlara dayanan farklı davranışlara gönderemezsiniz).

Doğru, bir yöntemin bağımsız değişkenlerine erişmenin bir yolu olduğu sürece yöntem aşırı yüklemesini simüle edebilir ve verdiğiniz bağımsız değişkenlere dayalı bir "if" yapısı kullanabilirsiniz. Ancak sözdizimsel şekeri düşünürseniz, aynı şekilde sözdizimsel şeker olmak için bir Turing makinesinin üzerinde herhangi bir şey düşünmeniz gerekir.


22
Aşırı yüklemeyi kaldırmak, dilin yapabileceklerini değiştirmez. Yine de eskisi gibi aynı şeyleri yapabilirsiniz; birkaç yöntemi yeniden adlandırmanız gerekir. Bu, desugaring döngülerinden daha önemsiz bir değişikliktir.
Doval

9
Dediğim gibi, tüm dillerin (makine dilleri dahil) bir Turing makinesinin üstünde sözdizimsel şeker olduğu yaklaşımını kullanabilirsiniz.
kdgregory

9
Bence yöntem aşırı Gördüğünüz gibi basitçe yapalım sum(numbersArray)ve sum(numbersList)yerine sumArray(numbersArray)ve sumList(numbersList). Doval'a katılıyorum, sadece sintatik şeker gibi görünüyor.
Aviv Cohn

3
Dilin çoğu. Uygulamayı deneyin instanceof, sınıflar, miras, arayüzler, jenerikler, yansıma veya kullanarak erişim belirteçleri if, whilebirlikte ve bağlaçlar, aynı semantik . Köşe kılıfı yok. Bu yapıların belirli kullanımlarıyla aynı şeyleri hesaplamanız için size meydan okumadığımı unutmayın. Boole mantığı ve dallanma / döngü kullanarak her şeyi hesaplayabileceğinizi zaten biliyorum . Sağladıkları statik garantiler de dahil olmak üzere bu dil özelliklerinin anlambiliminin mükemmel kopyalarını uygulamanızı istiyorum (derleme zamanı kontrolleri derleme zamanında yine de yapılmalıdır.)
Doval

6
@Doval, kdgregory: Sözdizimsel şekeri tanımlamak için, bazı semantiğe göre tanımlamanız gerekir. Sahip olduğunuz tek anlam, "Bu program ne hesaplıyor?" İse, o zaman her şeyin bir Turing makinesi için sadece sözdizimsel şeker olduğu açıktır. Öte yandan, nesneler ve üzerlerindeki belirli işlemler hakkında konuşabileceğiniz bir anlambiliminiz varsa, belirli sözdiziminin kaldırılması, dil hala Turing-complete olsa bile, bu işlemi daha fazla ifade etmenize izin vermez.
Giorgio

13

Sözdizimsel şeker terimi tipik olarak özelliğin bir ikame ile tanımlandığı vakaları ifade eder. Dil, bir özelliğin ne yaptığını tanımlamaz, bunun yerine tam olarak başka bir şeye eşdeğer olduğunu tanımlar. Örneğin, her döngü için

for(Object alpha: alphas) {
}

Oluyor:

for(Iterator<Object> iter = alpha.iterator(); iter.hasNext()) {
   alpha = iter.next();
}

Veya değişken argümanları olan bir işlev alın:

void foo(int... args);

foo(3, 4, 5);

Hangi olur:

void Foo(int[] args);

foo(new int[]{3, 4, 5});

Bu nedenle, özelliği diğer özellikler açısından uygulamak için önemsiz bir sözdizimi ikamesi vardır.

Yöntem aşırı yüklemesine bakalım.

void foo(int a);
void foo(double b);

foo(4.5);

Bu şu şekilde yeniden yazılabilir:

void foo_int(int a);
void foo_double(double b);

foo_double(4.5);

Ama buna eşdeğer değil. Java modeli içinde bu farklı bir şey. oluşturulacak foo(int a)bir foo_intişlev uygulamaz . Java, belirsiz işlevlere komik adlar vererek yöntem aşırı yüklemesi uygulamaz. Sözdizimsel şeker olarak saymak için, java'nın gerçekten yazdığınız foo_intve çalıştığınız gibi davranması gerekirdi foo_doubleama öyle değil.


2
Hiç kimsenin sözdizimi şekeri dönüşümünün önemsiz olduğunu söylemediğini sanmıyorum. Olsa bile But, the transformation isn't trivial. At the least, you have to determine the types of the parameters., türlerin belirlenmesi gerekmediği için çok kabataslak iddiayı buluyorum ; derleme zamanında biliniyorlar.
Doval

3
"Sözdizimsel şeker olarak saymak için, java gerçekten foo_int ve foo_double fonksiyonları yazmış gibi davranıyor olmalı, ama değil." - polimorfizmlerden değil aşırı yükleme yöntemlerinden bahsettiğimiz sürece foo(int)/ foo(double)ve foo_int/ arasında ne fark vardır foo_double? Java'yı çok iyi bilmiyorum ama böyle yeniden adlandırmanın gerçekten JVM'de gerçekleştiğini hayal ediyorum (iyi - muhtemelen foo(args)daha sonra kullanmak foo_args- sembol yönetimi ile en azından C ++ 'da (ok - sembol yönetimi teknik olarak bir uygulama detayıdır ve bir parçası değildir) ) dilin.
Maciej Piechotka

2
@Doval: "Hiç kimsenin sözdizimi şekeri dönüşümünün önemsiz olması gerektiğini söylemediğini sanmıyorum." - Doğru, ama yerel olmalı . Bildiğim sözdizimsel şekerin tek yararlı tanımı Matthias Felleisen'in dil anlatımıyla ilgili ünlü makalesinden ve temel olarak L + y dilinde yazılmış bir programı yeniden yazabiliyorsanız (yani , y özelliği olan bazı L dilleri ) L dili (yani, y'nin özelliği olmayan dilin bir alt kümesi ) programın genel yapısını değiştirmeden (yani yalnızca yerel değişiklikler yapmak), o zaman y L + y'de sözdizimsel şekerdir ve
Jörg W Mittag

2
L' nin ifadesini artırmaz . Eğer Ancak, olamaz da programın küresel yapısına değişiklik yapmak zorunda olmadığını, yani o, bunu, o zaman olduğu değil sözdizimsel şeker ve does gerçeği yapmak içinde L + y daha etkileyici L . Örneğin, gelişmiş fordöngüye sahip Java, onsuz Java'dan daha etkileyici değildir. (It adlı güzel, daha özlü, daha okunabilir ve tüm çevresinde daha iyi, ben iddia ediyorum, ama değil daha etkileyici.) Gerçi aşırı dava hakkında değil eminim. Emin olmak için muhtemelen kağıdı tekrar okumam gerekecek. Benim bağırsak diyor olduğunu sözdizimsel şeker, ama emin değilim.
Jörg W Mittag

2
@MaciejPiechotka, işlevlerin bu şekilde yeniden adlandırıldığı dil tanımının bir parçası olsaydı ve bu adlar altındaki işleve erişebilirseniz, sözdizimsel şeker olacağını düşünüyorum. Ama bir uygulama detayı olarak gizlendiği için, bunun sözdizimsel şeker olmasını diskalifiye ettiğini düşünüyorum.
Winston Ewert

8

Adı yönetmenin işe yaradığı göz önüne alındığında, sözdizimsel şekerden başka bir şey olması gerekmez mi?

Arayanın, aynı işlevi çağırdığını, hayal etmediğini hayal etmesini sağlar. Ancak tüm işlevlerinin gerçek isimlerini bilebilirdi . Sadece türlenmemiş bir değişkeni yazılan bir işleve geçirerek ve türünün oluşturulmasını sağlayarak gecikmeli polimorfizme ulaşmak mümkün olsaydı, çağrı isme göre doğru sürüme gidebilirdi, bu gerçek bir dil özelliği olurdu.

Ne yazık ki, bunu bir dilin görmemiştim. Belirsizlik olduğunda, bu derleyiciler bunu çözmez, yazarın onlar için çözmesi konusunda ısrar ederler.


Burada aradığınız özelliğe "Çoklu Gönderim" denir. Haskell, Scala ve (4.0'dan beri) C # dahil olmak üzere birçok dil destekliyor.
Iain Galloway

Sınıflardaki parametreleri düz yöntem aşırı yüklemesinden ayırmak istiyorum. Düz yöntem aşırı yükleme durumunda, programcı tüm sürümleri yazar, derleyici nasıl seçileceğini bilir. Bu sadece sözdizimsel şekerdir ve çoklu sevkiyat için bile basit isim yönetimi ile çözülür. --- Sınıflarda parametrelerin varlığında, derleyici kodu gerektiği gibi üretir ve bu tamamen değiştirir.
Jon Jay Obermark

2
Sanırım yanlış anladın. Bir yöntem için bir parametre ise, örneğin, C #, dynamicardından aşırı çözünürlüğü olmayan derleme zamanında, zamanında ortaya çıkar . Çoklu dağıtım budur ve fonksiyonları yeniden adlandırarak çoğaltılamaz.
Iain Galloway

Oldukça havalı. Yine de değişken tip için test yapabilirim, bu yüzden bu sadece sözdizimsel şeker üzerine yerleştirilmiş yerleşik bir işlevdir. Bir dil özelliğidir, ancak zar zor.
Jon Jay Obermark

7

Dile bağlı olarak sözdizimsel şekerdir ya da değildir.

Örneğin C ++ 'da, aşırı yükleme ve şablonlar kullanmadan, komplikasyonlar olmadan mümkün olmayan şeyleri yapabilirsiniz (şablonun tüm örneklerini manuel olarak yazın veya çok sayıda şablon parametresi ekleyin).

Dinamik gönderimin, bazı parametrelerde dinamik olarak çözülen bir aşırı yükleme biçimi olduğunu unutmayın (bazı diller için sadece özel bir dil, bu , ancak tüm diller çok sınırlı değildir) ve ben bu tür sözdizimsel şekeri aşırı yük olarak adlandırmayacağım.


Diğer cevapların aslında yanlış olduğunda nasıl daha iyi performans gösterdiğinden emin değilim.
Telastyn

5

Çağdaş diller için, sadece sözdizimsel şeker; tamamen dilden bağımsız bir şekilde, bundan daha fazlası.

Daha önce bu cevap basitçe sözdizimsel şekerden daha fazlasını ifade ediyordu, ancak yorumlarda görüyorsanız, Falco, çağdaş dillerin eksik olduğu bir bulmacanın bir parçası olduğu noktasını dile getirdi; aynı aşamada hangi işlevi çağıracaklarını dinamik belirleme ile yöntem aşırı yüklemesini karıştırmazlar. Bu daha sonra açıklanacaktır.

İşte bu yüzden daha fazla olmalı .

Hem yöntem aşırı yüklenmesini hem de türlenmemiş değişkenleri destekleyen bir dil düşünün. Aşağıdaki yöntem prototiplerine sahip olabilirsiniz:

bool someFunction(int arg);

bool someFunction(string arg);

Bazı dillerde, derleme zamanında bunlardan hangisinin belirli bir kod satırı tarafından çağrılacağını bilmek istifa edersiniz. Ancak bazı dillerde, tüm değişkenler yazılmaz (veya hepsi örtük olarak Objectveya herhangi bir şekilde yazılır ), bu nedenle anahtarları farklı türdeki değerlerle eşleşen bir sözlük oluşturduğunuzu düşünün:

dict roomNumber; // some hotels use numbers, some use letters, and some use
                 // alphanumerical strings.  In some languages, built-in dictionary
                 // types automatically use untyped values for their keys to map to,
                 // so it makes more sense then to allow for both ints and strings in
                 // your code.

O zaman, someFunctionbu oda numaralarından birine başvurmak istersen ? Buna şöyle diyorsunuz:

someFunction(roomNumber[someSortOfKey]);

Is someFunction(int)denir, ya da someFunction(string)aradı? Burada, özellikle üst düzey dillerde bunların tamamen dik yöntem olmadığı bir örnek görüyorsunuz. Dil, çalışma zamanı boyunca bunlardan hangisini çağıracağını bulmalıdır, bu yüzden bunları en azından bir şekilde aynı yöntem olarak görmelidir.

Neden sadece şablon kullanmıyorsunuz? Neden basitçe bir argüman kullanmıyorsunuz?

Esneklik ve ince taneli kontrol. Bazen şablonlar / türlenmemiş argümanlar kullanmak daha iyi bir yaklaşımdır, ancak bazen değildir.

Örneğin, her birinin bir intve bir stringbağımsız değişken olarak aldığı iki yöntem imzası olabileceği , ancak her imzada siparişin farklı olduğu durumları düşünmeniz gerekir . Bunu yapmak için iyi bir nedeniniz olabilir, çünkü her imzanın uygulaması büyük ölçüde aynı şeyi yapabilir, ancak sadece biraz farklı bir bükülme ile; örneğin, günlük kaydı farklı olabilir. Veya tam olarak aynı şeyi yapsalar bile, belirli bilgileri yalnızca argümanların belirtildiği sıradan otomatik olarak toplayabilirsiniz. Teknik olarak, geçirilen bağımsız değişkenlerin her birinin türünü belirlemek için sözde anahtar deyimlerini kullanabilirsiniz, ancak bu dağınık hale gelir.

Peki bu bir sonraki örnek kötü programlama uygulaması mı?

bool stringIsTrue(int arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(Object arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(string arg)
{
    if (arg == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

Evet, genel olarak. Bu özel örnekte, birisinin bunu belirli ilkel türlere uygulamaya çalışmasını ve beklenmedik davranışları geri kazanmasını engelleyebilir (ki bu iyi bir şey olabilir); ancak diyelim ki yukarıdaki kodu kısalttım ve aslında tüm ilkel tipler ve Objects için aşırı yüklemelere sahipsiniz . O zaman bu sonraki kod parçası gerçekten daha uygundur:

bool stringIsTrue(untyped arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

Ama bunu sadece ints ve strings için kullanmanız gerekiyorsa ve ya daha basit veya daha karmaşık koşullara göre doğru dönmesini istiyorsanız ne olur? Sonra aşırı yüklemeyi kullanmak için iyi bir nedeniniz var:

bool appearsToBeFirstFloor(int arg)
{
    if (arg.digitAt(0) == 1)
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool appearsToBeFirstFloor(string arg)
{
    string firstCharacter = arg.characterAt(0);
    if (firstCharacter.isDigit())
    {
        return appearsToBeFirstFloor(int(firstCharacter));
    }
    else if (firstCharacter.toUpper() == "A")
    {
        return true;
    }
    else
    {
        return false;
    }
}

Ama hey, neden bu işlevlere iki farklı isim vermiyorsunuz? Hala aynı miktarda ince taneli kontrole sahipsiniz, değil mi?

Çünkü, daha önce belirtildiği gibi, bazı oteller rakamlar kullanır, bazıları harf kullanır ve bazıları rakam ve harflerden oluşan bir karışım kullanır:

appearsToBeFirstFloor(roomNumber[someSortOfKey]);

// will treat ints and strings differently, without you having to write extra code
// every single spot where the function is being called

Bu hala gerçek hayatta kullanacağım kodun aynısı değil, ama gayet iyi yaptığım noktayı göstermeli.

Ama ... İşte bu yüzden çağdaş dillerde sözdizimsel şekerden daha fazlası değil.

Falco, yorumlarda mevcut dillerin temelde aynı adımda yöntem aşırı yüklemesi ve dinamik işlev seçimini karıştırmadığı noktasını dile getirdi. Daha önce belirli dilleri çalışmak için anladığım yol appearsToBeFirstFloor, yukarıdaki örnekte aşırı yüklenebilmenizdi ve dil, çalışma zamanında, türlenmemiş değişkenin çalışma zamanı değerine bağlı olarak, işlevin hangi sürümünün çağrılacağını belirleyecekti. Bu karışıklık kısmen, belirli bir kod satırında hangi fonksiyonun çalışma zamanında çağrıldığını kolayca rastgele ayarlayabileceğiniz ActionScript 3.0 gibi ECMA türleriyle çalışmaktan kaynaklanmıştır.

Bildiğiniz gibi, ActionScript 3 yöntem aşırı yüklemesini desteklemez. VB.NET'e gelince, bir türü açıkça atamadan değişkenleri bildirebilir ve ayarlayabilirsiniz, ancak bu değişkenleri aşırı yüklenmiş yöntemlere bağımsız değişken olarak geçirmeye çalıştığınızda, hangi yöntemi çağırmak istediğinizi belirlemek için çalışma zamanı değerini okumak istemez; bunun yerine, tür argümanları olan Objectveya olmayan türler veya buna benzer başka bir yöntem bulmak ister . Dolayısıyla, yukarıdaki intvs. stringörneği de bu dilde çalışmaz. C ++, boş bir işaretçi veya bunun gibi başka bir mekanizma kullandığınızda olduğu gibi benzer sorunlara sahiptir, yine de derleme zamanında türü el ile ayırmanızı gerektirir.

İlk başlığın dediği gibi ...

Çağdaş diller için, sadece sözdizimsel şeker; tamamen dilden bağımsız bir şekilde, bundan daha fazlası. Yukarıdaki örnekte olduğu gibi yöntem aşırı yüklemesini daha kullanışlı ve alakalı hale getirmek, aslında mevcut bir dile eklemek için iyi bir özellik olabilir (AS3 için yaygın olarak talep edildiği gibi) veya aynı zamanda birçok farklı temel sütun arasında yeni bir prosedür / nesne yönelimli dil oluşturulması.


3
Derleme zamanında değil, İşlev-Dağıtım'ı gerçekten işleyen herhangi bir dili adlandırabilir misiniz? Bildiğim TÜM diller derleme zamanı kesinliği gerektirir hangi işlev denir ...
Falco

@Falco ActionScript 3.0 bunu çalışma zamanında işler. : Sen, mesela, döner rastgele üç dizeleri biri ve daha sonra rastgele üç işlevlerinden birini çağırmak için dönüş değerini kullanan bir işlev kullanabilirsiniz this[chooseFunctionNameAtRandom](); ise chooseFunctionNameAtRandom()döner ya "punch", "kick"ya "dodge", o zaman thusly rastgele çok basit uygulayabilirsiniz örneğin bir Flash oyunundaki bir düşmanın yapay zekasındaki öğe.
Panzercrisis

1
Evet - ancak ikisi de dinamik işlev dağıtımı almak için gerçek anlamsal yöntemlerdir, Java da bunlara sahiptir. Ancak aşırı yüklenmeden farklıdırlar, aşırı yüklenme statik ve sadece sözdizimsel şekerdir, dinamik gönderme ve kalıtım ise yeni işlevler sunan gerçek dil özellikleridir!
Falco

1
... Ayrıca temel sınıf işaretçilerin yanı sıra C ++ 'da geçersiz işaretçileri de denedim, ancak derleyici bir işleve geçmeden önce kendimi netleştirmemi istedi. Şimdi bu cevabı silmek isteyip istemediğinizi merak ediyorum. Dillerin neredeyse her zaman dinamik fonksiyon seçimini aynı talimat veya ifadede fonksiyon aşırı yüklemesi ile birleştirmeye doğru gitmeye başlaması gibi görünmeye başlar, ancak son saniyede uzaklaşır. Yine de güzel bir dil özelliği olurdu; belki birinin buna sahip bir dil yapması gerekir.
Panzercrisis

1
Cevabın kalmasına izin verin, belki araştırmanızın bir kısmını cevaba yapılan yorumlardan eklemeyi düşünün?
Falco

2

Gerçekten "sözdizimsel şeker" tanımınıza bağlıdır. Aklıma gelen bazı tanımları ele almaya çalışacağım:

  1. Bir özellik, onu kullanan bir program her zaman özelliği kullanmayan bir başka dile çevrilebildiğinde sözdizimsel şekerdir.

    Burada çevrilemeyen ilkel bir özellik grubu olduğunu varsayıyoruz: başka bir deyişle, "özellik X'i kullanarak X özelliğini değiştiremezsiniz" ve "Y özelliğini X özelliği ile değiştirebilirsiniz" türünde hiçbir döngü yoktur. İkisinden biri doğruysa, diğer özellik ilkinden farklı olmayan veya ilkel bir özellik olarak ifade edilebilir.

  2. Tanım 1 ile aynıdır, ancak çevrilen programın birincisi kadar tipte güvenli olması şartıyla, yani desugaring ile herhangi bir bilgiyi kaybetmezsiniz.

  3. OP'nin tanımı: bir özellik, çevirisinin programın yapısını değiştirmediği, ancak yalnızca "yerel değişiklikler" gerektirdiği durumlarda sözdizimsel şekerdir.

Haskell'i aşırı yükleme için örnek olarak ele alalım. Haskell, tip sınıfları aracılığıyla kullanıcı tanımlı aşırı yükleme sağlar. Örneğin, +ve *işlemleri Numtype sınıfında tanımlanır ve bu sınıfın (complete) örneğine sahip herhangi bir tür ile kullanılabilir +. Örneğin:

instance Num a => Num (b, a) where
    (x, y) + (_, y') = (x, y + y')
    -- other definitions

("Hello", 1) + ("World", 3) -- -> ("Hello", 4)

Haskell'in tip sınıfları hakkında iyi bilinen bir şey onlardan kurtulabilmenizdir . Yani tip sınıflarını kullanan herhangi bir programı, onları kullanmayan eşdeğer bir programda çevirebilirsiniz.

Çeviri oldukça basit:

  • Bir sınıf tanımı verildi:

    class (P_1 a, ..., P_n a) => X a where
        op_1 :: t_1   ... op_m :: t_m
    

    Bir cebirsel veri türüne çevirebilirsiniz:

    data X a = X {
        X_P_1 :: P_1 a, ... X_P_n :: P_n a,
        X_op_1 :: t_1, ..., X_op_m :: t_m
    }
    

    Buraya X_P_ive X_op_ivardır seçiciler . Yani tipi bir değer verilir X auygulanması X_P_1bu nedenle tip fonksiyonlar, bunu, alanda saklanan değer döndürür değerine X a -> P_i a(ya da X a -> t_i).

    Bir İçin Çok kaba anology türe ait değerlerin düşünebildiğim X aolarak structo zaman s ve xtiptedir X aifadeler:

    X_P_1 x
    X_op_1 x
    

    şöyle görülebilir:

    x.X_P_1
    x.X_op_1
    

    (Adlandırılmış alanlar yerine yalnızca konum alanlarını kullanmak kolaydır, ancak adlandırılmış alanların örneklerde işlenmesi ve bazı kazan plakası kodlarından kaçınma daha kolaydır).

  • Bir örnek bildirimi verildiğinde:

    instance (C_1 a_1, ..., C_n a_n) => X (T a_1 ... a_n) where
        op_1 = ...; ...;  op_m = ...
    

    Bunu sözlükler için verilen bir işleve çevirebilirsiniz. C_1 a_1, ..., C_n a_nSınıflar bir sözlük değeri (yani bir tür değeri X a) döndürenT a_1 ... a_n .

    Başka bir deyişle, yukarıdaki örnek aşağıdaki gibi bir işleve çevrilebilir:

    f :: C_1 a_1 -> ... -> C_n a_n -> X (T a_1 ... a_n)
    

    (Bunu not et n olabileceğini0 ).

    Aslında bunu şu şekilde tanımlayabiliriz:

    f c1 ... cN = X {X_P_1=get_P_1_T, X_P_n=get_P_n_T,
                     X_op_1=op_1, ..., X_op_m=op_m}
        where
            op_1 = ...
            ...
            op_m = ...
    

    burada op_1 = ...için op_m = ...tanımlar bulunan instancebeyan ve get_P_i_Ttanımlanan fonksiyonları P_iörneği Ttürü (Çünkü bu mevcut olmalıdırP_i s üst sınıfı vardır X).

  • Aşırı yüklenmiş bir işleve çağrı verildiğinde:

    add :: Num a => a -> a -> a
    add x y = x + y
    

    Sözlükleri sınıf kısıtlamalarına göre açıkça geçirebilir ve eşdeğer bir çağrı alabiliriz:

    add :: Num a -> a -> a -> a
    add dictNum x y = ((+) dictNum) x y
    

    Sınıf kısıtlamalarının nasıl yeni bir argüman haline geldiğine dikkat edin. +Önce açıklandığı gibi tercüme programda seçicidir. Başka bir deyişle, addargümanının türü için sözlük verildiğinde , çevrilen işlev, sonucu kullanarak hesaplamak için asıl işlevi "paketinden" (+) dictNumçıkaracak ve daha sonra bu işlevi argümanlara uygulayacaktır.

Bu, her şey hakkında çok hızlı bir taslak. Eğer ilgileniyorsanız Simon Peyton Jones ve ark.

Ben de benzer bir yaklaşım inanıyoruz olabilir çok diğer dillerde aşırı kullanılacak.

Ancak bu, sözdizimsel şeker tanımınızın (1) olduğunu gösterir. aşırı yüklenmenin sözdizimsel şeker olduğunu gösterir . Çünkü ondan kurtulabilirsiniz.

Ancak çevrilen program orijinal program hakkında bazı bilgileri kaybeder. Örneğin, üst sınıfların örneklerinin var olmasını zorunlu kılmaz. (Ebeveynin sözlüklerini ayıklama işlemleri hala bu türden olsa da, undefineddiğer polimorfik değerleri geçirebilir veya böylece değerleri oluşturmadan bir değer oluşturabilirsiniz X y.P_i y , böylece çeviri tümüyle kaybolmaz tipi güvenlik). Dolayısıyla (2) 'ye göre syntacti şekeri değildir.

(3) 'e gelince. Cevabın evet mi yoksa hayır mı olduğunu bilmiyorum.

Hayır diyorum, örneğin, bir örnek bildirimi işlev tanımı haline gelir. Aşırı yüklenmiş işlevler yeni bir parametre alır (yani hem tanımı hem de tüm çağrıları değiştirir).

Evet diyorum, çünkü iki program hala birebir eşleşiyor, bu yüzden "yapı" aslında çok fazla değişmiyor.


Bu, aşırı yüklenmenin getirdiği pragmatik avantajların o kadar büyük olduğunu söyleyebilirim ki, "sözdizimsel şeker" gibi "aşağılayıcı" bir terim kullanmak doğru görünmüyor.

Tüm Haskell sözdizimini çok basit bir Çekirdek dile çevirebilirsiniz (bu derleme yapılırken yapılır), bu yüzden Haskell sözdiziminin çoğu sadece lambda-calculus artı biraz yeni yapılar için "sözdizimsel şeker" olarak görülebilir. Bununla birlikte, Haskell programlarının işlenmesinin çok daha kolay ve özlü olduğunu kabul edebiliriz, oysa tercüme edilen programların okunması veya üzerinde düşünülmesi oldukça zordur.


2

Dağıtım derleme zamanında çözülürse, yalnızca bağımsız değişken ifadesinin statik türüne bağlı olarak, programlayıcının statik türü "bilmesi" koşuluyla, iki farklı yöntemin farklı adlarla değiştirilmesini kesinlikle "sözdizimsel şeker" olarak söyleyebilirsiniz. aşırı yüklenmiş ad yerine doğru yöntem adını kullanabilir. Aynı zamanda bir statik polimorfizm biçimidir, ancak bu sınırlı biçimde genellikle çok güçlü değildir.

Elbette, bir değişkenin türünü her değiştirdiğinizde çağırdığınız yöntemlerin adlarını değiştirmek bir sıkıntı olacaktır, ancak örneğin C dilinde yönetilebilir bir sıkıntı olarak kabul edilir, bu nedenle C'nin aşırı işlev yüklemesi yoktur ( artık genel makroları var).

C ++ şablonlarında ve önemsiz olmayan statik tür kesinti yapan herhangi bir dilde, statik tür kesintiğinin "sözdizimsel şeker" olduğunu iddia etmedikçe, bunun "sözdizimsel şeker" olduğunu gerçekten iddia edemezsiniz. Şablonlara sahip olmamak bir sıkıntı olurdu ve C ++ bağlamında, "yönetilemez bir rahatsızlık" olurdu, çünkü bunlar dil ve standart kütüphaneleri için çok deyimseldir. C ++ 'da güzel bir yardımcıdan çok daha fazlası, dilin stili için önemlidir ve bu yüzden bunu "sözdizimsel şeker" den daha fazla çağırmanız gerektiğini düşünüyorum.

Java'da, örneğin kaç tane aşırı yükleme olduğunu PrintStream.printve göz önünde bulundurarak sadece bir kolaylıktan daha fazlasını düşünebilirsiniz PrintStream.println. Ama sonra, DataInputStream.readXJava dönüş türüne aşırı yüklenmediği için birçok yöntem var , bu yüzden bir anlamda sadece kolaylık sağlamak için. Bunların hepsi ilkel tipler içindir.

Ben sınıfları varsa Java ne hatırlamıyorum Ave Buzanan O, ben yöntemleri aşırı foo(O), foo(A)ve foo(B)ile genel bir sonra ve <T extends O>ben çağrı foo(t)nerede tbir örneğidir T. Durumda Tolduğunu Aben aşırı dayalı sevk alabilirim ya da aradım sanki olduğunu foo(O)?

Eskiyse, Java yöntemi aşırı yüklemeleri, C ++ aşırı yüklemeleri gibi şekerden daha iyidir. Tanımınızı kullanarak, sanırım Java'da yerel olarak bir dizi tip kontrol yazabilirim (bu, kırılgan olurdu, çünkü yeni aşırı yüklemeler fooek kontroller gerektirecektir). Bu kırılganlığı kabul etmenin yanı sıra, doğru yapmak için çağrı sitesinde yerel bir değişiklik yapamam, bunun yerine genel kod yazmayı bırakmam gerekir . Şişirilmiş kodun önlenmesinin sözdizimsel şeker olabileceğini, ancak kırılgan kodun önlenmesinin bundan daha fazla olduğunu iddia ediyorum. Bu nedenle, genel olarak statik polimorfizm sadece sözdizimsel şekerden daha fazlasıdır. Belirli bir dilde durum, dilin statik türü "bilmeyerek" elde etmenize ne kadar izin verdiğine bağlı olarak farklı olabilir.


Java'da aşırı yükler derleme zamanında giderilir. Tip silme kullanımı göz önüne alındığında, başka türlü olmaları imkansız olacaktır. Bundan başka, eğer hatta tip silinmeye olmadan T:Animaltürüdür edilir SiameseCatve mevcut aşırı yükler vardır Cat Foo(Animal), SiameseCat Foo(Cat)ve Animal Foo(SiameseCat)eğer hangi aşırı yük seçilmelidir Tolduğunu SiameseCat?
supercat

@supercat: mantıklı. Bu yüzden cevabı hatırlamadan anlayabilirdim (ya da elbette çalıştırırım). Bu nedenle, Java aşırı yüklemeleri şekerden daha iyi değildir , aynı şekilde C ++ aşırı yüklemeleri genel kodla ilişkilidir. Sadece yerel bir dönüşümden daha iyi oldukları başka bir yol var. Örneğimi C ++ olarak değiştirmeli miyim, ya da bazı hayal edilen Java olarak bırakmamalı mı?
Steve Jessop

Aşırı yükler, yöntemlerin isteğe bağlı argümanları olduğu durumlarda yararlı olabilir, ancak bunlar da tehlikeli olabilir. Çizginin olarak long foo=Math.round(bar*1.0001)*5değiştirildiğini varsayalım long foo=Math.round(bar)*5. Bu bar, örneğin, 123456789L'ye eşitse anlambilimi nasıl etkiler ?
supercat

Gerçek tehlike iddia ediyorum @supercat gelen örtük dönüştürme yoktur longiçin double.
Doval

@Doval: Kime double?
supercat

1

"Sözdizimsel şeker", işe yaramaz veya anlamsız gibi aşağılayıcı geliyor. Bu yüzden soru birçok olumsuz yanıtı tetikliyor.

Ancak haklısınız, yöntem aşırı yükleme, farklı yöntemler için aynı adı kullanma olasılığı dışında dile herhangi bir özellik eklemiyor. Parametre türünü açık yapabilirsiniz, program yine de aynı şekilde çalışacaktır.

Aynı şey paket adları için de geçerlidir. String sadece java.lang.String için sözdizimsel şekerdir.

Aslında, böyle bir yöntem

void fun(int i, String c);

sınıfta MyClass "my_package_MyClass_fun_int_java_lang_String" gibi bir şey olarak adlandırılmalıdır. Bu yöntemi benzersiz bir şekilde tanımlayacaktır. (JVM dahili olarak böyle bir şey yapar). Ama bunu yazmak istemiyorsun. Bu yüzden derleyici eğlenceli yazmanıza (1, "bir") ve hangi yöntem olduğunu belirlemenize izin verecektir.

Ancak aşırı yükleme ile yapabileceğiniz bir şey vardır: Aynı sayıda argümanla bir yöntemi aşırı yüklerseniz, derleyici hangi versiyonun argümanlarla eşleştirildiğini, sadece eşit türlerle değil, aynı zamanda verilen argüman bildirilen argümanın bir alt sınıfıdır.

Aşırı yüklenmiş iki yordamınız varsa

addParameter(String name, Object value);
addParameter(String name, Date value);

Tarihler için prosedürün belirli bir sürümü olduğunu bilmenize gerek yoktur. addParameter ("merhaba", "dünya) ilk sürümü çağırır, addParameter (" şimdi ", yeni Tarih ()) ikinci sürümü çağırır.

Tabii ki, bir yöntemi tamamen farklı bir şey yapan başka bir yöntemle aşırı yüklemekten kaçınmalısınız.


1

İlginçtir, bu sorunun cevabı dile bağlı olacaktır.

Özellikle, aşırı yükleme ve genel programlama (*) arasında bir etkileşim vardır ve genel programlamanın nasıl uygulandığına bağlı olarak, sadece sözdizimsel şeker (Rust) veya kesinlikle gerekli olabilir (C ++).

Yani, jenerik programlama açık arayüzlerle (Rust veya Haskell'de uygulandığında, bunlar tip sınıfları olacaktır), aşırı yükleme sadece sözdizimsel şekerdir; hatta dilin bir parçası bile olmayabilir.

Öte yandan, genel programlama ördek yazarak (dinamik veya statik olsun) uygulandığında , yöntemin adı temel bir sözleşmedir ve bu nedenle sistemin çalışması için aşırı yükleme zorunludur.

(*) Tek tip bir yöntem yazmak için, çeşitli tipler üzerinde düzgün bir şekilde çalışmak için kullanılır.


0

Bazı dillerde şüphesiz sadece sözdizimsel şekerdir. Bununla birlikte, şekerin ne olduğu sizin bakış açınıza bağlıdır. Bu tartışmayı daha sonra bu cevapta bırakacağım.

Şimdilik, bazı dillerde kesinlikle sözdizimsel şeker olmadığını belirtmek isterim. En azından aynı şeyi uygulamak için tamamen farklı bir mantık / algoritma kullanmanıza gerek kalmadan. Özyinelemenin sözdizimsel şeker olduğunu iddia etmek gibidir (bu, tüm özyinelemeli algoritmayı bir döngü ve bir yığınla yazabilmenizdir).

Değiştirilmesi çok zor bir kullanım örneği, ironik bir şekilde bu özelliğe "işlev aşırı yüklenmesi" adını vermeyen bir dilden gelir. Bunun yerine "desen eşleştirme" olarak adlandırılır (yalnızca türlerin değil değerlerin de aşırı yüklenebilmesi nedeniyle aşırı yüklemenin üst kümesi olarak görülebilir).

İşte Haskell'deki Fibonacci fonksiyonunun klasik saf uygulaması:

fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

Muhtemelen üç işlev, başka bir dilde yaygın olarak yapıldığı için bir if / else ile değiştirilebilir. Ancak bu temelde son derece basit bir tanımdır:

fib n = fib (n-1) + fib (n-2)

çok daha karışıktır ve Fibonacci dizisinin matematiksel kavramını doğrudan ifade etmez.

Bu nedenle, tek kullanımlık bir işlevi farklı argümanlarla çağırmanıza izin vermekse, bazen sözdizimi şekeri olabilir. Ancak bazen bundan çok daha temeldir.


Şimdi hangi operatör aşırı yüklenmesinin bir şeker olabileceğinin tartışılması için. Bir kullanım örneği belirlediniz - farklı argümanlar alan benzer işlevleri uygulamak için kullanılabilir. Yani:

function print (string x) { stdout.print(x) };
function print (number x) { stdout.print(x.toString) };

alternatif olarak şu şekilde uygulanabilir:

function printString (string x) {...}
function printNumber (number x) {...}

ya da:

function print (auto x) {
    if (x instanceof String) {...}
    if (x instanceof Number) {...}
}

Ancak operatör aşırı yüklemesi, isteğe bağlı bağımsız değişkenlerin uygulanması için bir şeker de olabilir (bazı dillerde operatör aşırı yüklenmesi vardır, ancak isteğe bağlı bağımsız değişkenler yoktur):

function print (string x) {...}
function print (string x, stream io) {...}

aşağıdakileri uygulamak için kullanılabilir:

function print (string x, stream io=stdout) {...}

Böyle bir dilde (google "Ferite dili") operatör aşırı yüklemesini kaldırmak, bir özelliği - isteğe bağlı argümanları büyük ölçüde kaldırır. Her iki özelliğe (c ++) sahip dillerden birinin veya diğerinin kaldırılması net bir etkiye sahip olmayacaktır, çünkü her ikisi de isteğe bağlı bağımsız değişkenleri uygulamak için kullanılabilir.


Haskell, operatör aşırı yüklemesinin neden sözdizimsel şeker olmadığının iyi bir örneğidir, ancak bence daha iyi bir örnek, desen eşleme ile (desen eşleştirme olmadan imkansız olduğunu bildiğim kadarıyla) cebirsel bir veri türünün yapıbozuması olacaktır.
11684

@ 11684: Bir örneğe işaret edebilir misiniz? Dürüst olmak gerekirse Haskell'i hiç bilmiyorum ama fib örneğini (youtube'da computerphile'de) gördüğümde desen eşleşmesini oldukça zarif buldum.
slebetman

data PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfoSizin gibi bir veri türü verildiğinde tip yapıcıları üzerinde desen eşleşmesi olabilir.
11684

Bunun gibi: getPayment :: PaymentInfo -> a getPayment CashOnDelivery = error "Should have been paid already" getPayment (Adress addr) = -- code to notify administration to send a bill getPayment (UserInvoice cust) = --whatever. I took the data type from a Haskell tutorial and have no idea what an invoice is. Umarım bu yorum biraz anlaşılabilir.
11684

0

Çoğu dilde basit sözdizimsel şeker olduğunu düşünüyorum (en azından bildiğim kadarıyla ...) çünkü hepsi derleme zamanında net olmayan bir İşlev çağrısı gerektirir. Ve derleyici sadece fonksiyon çağrısını doğru uygulama imzasına açık bir işaretçi ile değiştirir.

Java örneği:

String s; int i;
mangle(s);  // Translates to CALL ('mangle':LString):(s)
mangle(i);  // Translates to CALL ('mangle':Lint):(i)

Sonuç olarak, aşırı yüklenmiş İşlev mangleını mangle_String ve mangle_int ile değiştirerek, arama ve değiştirme ile basit bir derleyici makrosu ile tamamen değiştirilebilir - argüman listesi nihai işlev tanımlayıcısının bir parçası olduğundan, bu neredeyse gerçekleşir -> ve bu nedenle sadece sözdizimsel şekerdir.

Şimdi, fonksiyonun çalışma zamanında gerçekten belirlendiği bir dil varsa, nesnelerdeki geçersiz kılınan Yöntemler gibi, bu farklı olacaktır. Ancak böyle bir dil olduğunu düşünmüyorum, çünkü method.overloading, derleyicinin çözemediği ve programcı tarafından açık bir kadro ile ele alınması gereken belirsizliğe eğilimlidir. Bu çalışma zamanında yapılamaz.


0

Java türünde bilgi derlenir ve aşırı yüklenmelerden hangisine çağrılır derleme zamanında karar verilir.

Aşağıdaki, sun.misc.UnsafeEclipse'nin sınıf dosya düzenleyicisinde görüldüğü gibi (Atomics için yardımcı program) adlı bir snippet'tir .

  // Method descriptor #143 (Ljava/lang/Object;I)I (deprecated)
  // Stack: 4, Locals: 3
  @java.lang.Deprecated
  public int getInt(java.lang.Object arg0, int arg1);
    0  aload_0 [this]
    1  aload_1 [arg0]
    2  iload_2 [arg1]
    3  i2l
    4  invokevirtual sun.misc.Unsafe.getInt(java.lang.Object, long) : int [231]
    7  ireturn
      Line numbers:
        [pc: 0, line: 213]

Gördüğünüz gibi, çağrılan yöntemin (hat 4) tür bilgisini içerir.

Bu, tür bilgisi alan bir java derleyicisi oluşturabileceğiniz anlamına gelir. Örneğin, böyle bir gösterimi kullanarak yukarıdakilerin kaynağı şöyle olur:

@Deprecated
public in getInt(Object arg0, int arg1){
     return getInt$(Object,long)(arg0, arg1);
}

ve uzun süre kullanmanız isteğe bağlı olacaktır.

Statik olarak yazılan diğer derlenmiş dillerde, derleyicinin türlere bağlı olarak hangi aşırı yükün çağrılacağına karar vereceği ve bağlama / çağrıya dahil edeceği benzer bir kurulum göreceksiniz.

Bunun istisnası, tip bilgilerinin dahil edilmediği ve aşırı yüklenmiş bir işlev oluşturmaya çalışmanın C dinamik kitaplıklarıdır.

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.