Czy metoda przeciążająca jest rodzajem polimorfizmu? Wydaje mi się, że to po prostu różnicowanie metod o tej samej nazwie i różnych parametrach. Tak stuff(Thing t)
i stuff(Thing t, int n)
są całkowicie różne metody miarę kompilator i runtime są zainteresowane.
Stwarza iluzję po stronie dzwoniącego, że jest to ta sama metoda, która działa inaczej na różne rodzaje obiektów - polimorfizm. Ale to tylko złudzenie, bo w rzeczywistości stuff(Thing t)
i stuff(Thing t, int n)
są całkowicie różne metody.
Czy metoda przeciążania jest czymś więcej niż cukrem syntaktycznym? Czy coś brakuje?
Powszechną definicją cukru syntaktycznego jest to, że jest on wyłącznie lokalny . Oznacza to, że zmiana fragmentu kodu na jego „słodzony” odpowiednik lub odwrotnie, wiąże się z lokalnymi zmianami, które nie wpływają na ogólną strukturę programu. I myślę, że przeciążanie metod dokładnie pasuje do tego kryterium. Spójrzmy na przykład pokazujący:
Rozważ klasę:
class Reader {
public String read(Book b){
// .. translate the book to text
}
public String read(File b){
// .. translate the file to text
}
}
Teraz rozważ inną klasę, która korzysta z tej klasy:
/* 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
}
}
W porządku. Zobaczmy teraz, co należy zmienić, jeśli zastąpimy przeciążenie metody zwykłymi metodami:
Te read
metody Reader
zmiany readBook(Book)
i readFile(file)
. Tylko kwestia zmiany ich nazw.
Kod wywołujący FileProcessor
zmienia się nieznacznie: reader.read(file)
zmienia się na reader.readFile(file)
.
I to wszystko.
Jak widać, różnica między używaniem przeciążenia metody a nieużywaniem jej jest czysto lokalna . I dlatego uważam, że kwalifikuje się jako czysty cukier syntaktyczny.
Chciałbym usłyszeć twoje zastrzeżenia, jeśli masz jakieś, może coś mi umknęło.
źródło
Odpowiedzi:
Aby odpowiedzieć na to pytanie, najpierw potrzebujesz definicji „cukru syntaktycznego”. Pójdę z Wikipedią :
Tak więc, zgodnie z tą definicją, funkcje takie jak varargs Javy lub zrozumienie Scali są cukrem syntaktycznym: tłumaczą się na podstawowe funkcje językowe (w pierwszym przypadku tablica, w drugim wywołania map / flatmap / filter), a ich usunięcie spowodowałoby nie zmieniaj rzeczy, które możesz zrobić z językiem.
Jednak przeciążenie metodami nie jest cukrem składniowym w tej definicji, ponieważ usunięcie go zasadniczo zmieniłoby język (nie byłoby już możliwe wysyłanie odrębnych zachowań opartych na argumentach).
To prawda, że możesz symulować przeciążenie metody, o ile masz jakiś sposób na uzyskanie dostępu do argumentów metody, i możesz użyć konstrukcji „jeśli” na podstawie podanych argumentów. Ale jeśli weźmiesz pod uwagę cukier syntaktyczny, będziesz musiał rozważyć wszystko nad maszyną Turinga, aby podobnie był cukrem syntaktycznym.
źródło
sum(numbersArray)
isum(numbersList)
zamiastsumArray(numbersArray)
isumList(numbersList)
. Zgadzam się z Doval, wygląda na zwykły cukier syntetyczny.instanceof
, klas, dziedziczenie, interfejsy, rodzajowych, odbicie lub specyfikatory dostępu używaneif
,while
i operatorów logicznych, z dokładnie tych samych semantyki . Brak skrzynek narożnych. Pamiętaj, że nie wymagam od ciebie obliczania tych samych rzeczy, co określone zastosowania tych konstrukcji. Wiem już , że możesz obliczyć wszystko przy użyciu logiki logicznej i rozgałęziania / zapętlania. Proszę o zaimplementowanie doskonałych kopii semantyki tych funkcji językowych, w tym wszelkich statycznych gwarancji, które zapewniają (kontrole czasu kompilacji muszą być nadal wykonywane w czasie kompilacji.)Termin cukier syntaktyczny zazwyczaj odnosi się do przypadków, w których cecha jest zdefiniowana przez podstawienie. Język nie określa, co robi funkcja, ale określa, że jest dokładnie równoważny z czymś innym. Na przykład dla każdej pętli
Staje się:
Lub weź funkcję ze zmiennymi argumentami:
Który staje się:
Istnieje więc trywialne podstawienie składni w celu zaimplementowania tej funkcji w odniesieniu do innych funkcji.
Spójrzmy na przeciążenie metody.
Można to przepisać jako:
Ale to nie jest równoważne. W modelu Java jest to coś innego.
foo(int a)
nie implementujefoo_int
funkcji, która ma zostać utworzona. Java nie implementuje przeciążania metod, nadając dwuznacznym funkcjom śmieszne nazwy. Aby liczyć się jako cukier składniowy, Java musiałaby udawać, że naprawdę napisałeśfoo_int
ifoo_double
działa, ale tak nie jest.źródło
But, the transformation isn't trivial. At the least, you have to determine the types of the parameters.
bardzo szkicowe, ponieważ nie trzeba określać typów ; są znane w czasie kompilacji.foo(int)
/foo(double)
ifoo_int
/foo_double
? Nie znam się zbyt dobrze na Javie, ale wyobrażam sobie, że taka zmiana nazwy tak naprawdę dzieje się w JVM (cóż - prawdopodobniefoo(args)
raczej wtedyfoo_args
- robi to przynajmniej w C ++ z manglingiem symboli (ok - mangling symboli jest technicznie szczegółem implementacji, a nie częścią) języka)for
pętli nie jest bardziej ekspresyjna niż Java bez niej. (Jest ładniejszy, bardziej zwięzły, bardziej czytelny i ogólnie lepszy, twierdziłbym, ale nie bardziej wyrazisty.) Jednak nie jestem pewien co do przypadku przeładowania. Pewnie będę musiał ponownie przeczytać artykuł. Moje jelita mówią, że to cukier składniowy, ale nie jestem pewien.Biorąc pod uwagę, że zmiana nazwy działa, czy nie musi to być jedynie cukier składniowy?
Pozwala to dzwoniącemu wyobrazić sobie, że wywołuje tę samą funkcję, kiedy tak nie jest. Ale znał prawdziwe nazwiska wszystkich swoich funkcji. Tylko jeśli możliwe byłoby osiągnięcie opóźnionego polimorfizmu poprzez przekazanie zmiennej bez typu do funkcji typowanej i ustalenie jej typu, aby wywołanie mogło przejść do właściwej wersji według nazwy, byłoby to prawdziwą cechą językową.
Niestety, nigdy nie widziałem, żeby język to robił. Kiedy występuje dwuznaczność, te kompilatory nie rozwiązują tego, nalegają, aby pisarz go rozwiązał.
źródło
dynamic
wówczas rozwiązanie przeciążenia występuje w czasie wykonywania, a nie w czasie kompilacji . Taka jest wielokrotna wysyłka i nie można jej replikować przez zmianę nazw funkcji.W zależności od języka jest to cukier składniowy lub nie.
Na przykład w C ++ możesz robić rzeczy przy użyciu przeciążenia i szablonów, co nie byłoby możliwe bez komplikacji (ręcznie wpisz wszystkie wystąpienia szablonu lub dodaj wiele parametrów szablonu).
Zauważ, że dynamiczna wysyłka jest formą przeciążania, dynamicznie rozwiązywaną dla niektórych parametrów (dla niektórych języków tylko jeden specjalny, to , ale nie wszystkie języki są tak ograniczone), i nie nazwałbym tego rodzaju przeciążeniem cukrem składniowym.
źródło
W przypadku współczesnych języków jest to po prostu cukier składniowy; w sposób całkowicie niezależny od języka, to coś więcej.
Wcześniej ta odpowiedź mówiła po prostu, że to coś więcej niż cukier syntaktyczny, ale jeśli zauważysz w komentarzach, Falco podniósł kwestię, że istnieje jedna część układanki, której brakuje współczesnym językom; nie łączą przeciążenia metody z dynamicznym określaniem, którą funkcję wywołać w tym samym kroku. Wyjaśnimy to później.
Oto dlaczego powinno być więcej.
Rozważ język, który obsługuje zarówno przeciążanie metod, jak i zmienne bez typów. Możesz mieć następujące prototypy metody:
W niektórych językach prawdopodobnie będziesz skłonny wiedzieć w czasie kompilacji, który z nich zostałby wywołany przez dany wiersz kodu. Ale w niektórych językach nie wszystkie zmienne są wpisywane (lub wszystkie są domyślnie wpisywane jako
Object
lub cokolwiek innego), więc wyobraź sobie zbudowanie słownika, którego klucze odwzorowują wartości różnych typów:A co teraz, jeśli chcesz złożyć podanie
someFunction
na jeden z tych numerów pokoi? Nazywasz to:Jest
someFunction(int)
nazywany lub jestsomeFunction(string)
nazywany? Oto jeden przykład, w którym nie są to całkowicie ortogonalne metody, szczególnie w językach wyższego poziomu. Język musi ustalić - w czasie wykonywania - który z nich wywołać, więc nadal musi traktować je jako przynajmniej w pewnym stopniu tę samą metodę.Dlaczego nie skorzystać z szablonów? Dlaczego nie użyć po prostu nietypowego argumentu?
Elastyczność i dokładniejsza kontrola. Czasem użycie szablonów / nietypowych argumentów jest lepszym podejściem, ale czasem nie.
Musisz pomyśleć o przypadkach, w których na przykład możesz mieć dwie sygnatury metod, z których każda przyjmuje argumenty
int
istring
jako argument, ale gdzie kolejność jest inna w każdej sygnaturze. Być może masz dobry powód, aby to zrobić, ponieważ implementacja każdego podpisu może w dużej mierze zrobić to samo, ale z nieco innym zwrotem; rejestrowanie może być na przykład inne. Lub nawet jeśli robią to samo dokładnie, możesz być w stanie automatycznie zebrać pewne informacje tylko z kolejności, w której argumenty zostały określone. Technicznie rzecz biorąc, możesz po prostu użyć instrukcji pseudo-switch, aby określić typ każdego z przekazywanych argumentów, ale robi się to bałagan.Czy ten kolejny przykład jest złą praktyką programistyczną?
Tak, ogólnie rzecz biorąc. W tym konkretnym przykładzie może powstrzymać kogoś od prób zastosowania tego do pewnych prymitywnych typów i odzyskania nieoczekiwanego zachowania (co może być dobrą rzeczą); ale załóżmy, że skróciłem powyższy kod i że w rzeczywistości masz przeciążenia dla wszystkich typów pierwotnych, a także dla
Object
s. Zatem ten następny fragment kodu jest naprawdę bardziej odpowiedni:Ale co, jeśli musisz użyć tego tylko dla
int
s istring
s, a co, jeśli chcesz, aby zwrócił prawdę na podstawie odpowiednio prostszych lub bardziej skomplikowanych warunków? Masz dobry powód, aby użyć przeciążenia:Ale hej, dlaczego nie nadać tym funkcjom dwóch różnych nazw? Nadal masz taką samą drobnoziarnistą kontrolę, prawda?
Ponieważ, jak wspomniano wcześniej, niektóre hotele używają liczb, niektóre używają liter, a niektóre używają kombinacji cyfr i liter:
To wciąż nie jest dokładnie ten sam kod, którego używałbym w prawdziwym życiu, ale powinien on ilustrować punkt, który robię dobrze.
Ale ... Oto dlaczego we współczesnych językach jest to cukier syntaktyczny.
Falco podniósł w komentarzach, że obecne języki w zasadzie nie łączą przeciążenia metod i dynamicznego wyboru funkcji w tym samym kroku. Sposób, w jaki wcześniej rozumiałem niektóre języki, był taki, że można przeciążać
appearsToBeFirstFloor
w powyższym przykładzie, a następnie język określałby w czasie wykonywania, która wersja funkcji ma zostać wywołana, w zależności od wartości środowiska uruchomieniowego zmiennej bez typu. To zamieszanie częściowo wynikało z pracy z językami podobnymi do ECMA, takimi jak ActionScript 3.0, w których można z łatwością losować, która funkcja jest wywoływana w określonym wierszu kodu w czasie wykonywania.Jak zapewne wiesz, ActionScript 3 nie obsługuje przeciążania metod. Jeśli chodzi o VB.NET, możesz deklarować i ustawiać zmienne bez jawnego przypisywania typu, ale kiedy próbujesz przekazać te zmienne jako argumenty do przeciążonych metod, nadal nie chce odczytać wartości środowiska wykonawczego w celu ustalenia, którą metodę wywołać; zamiast tego chce znaleźć metodę z argumentami typu
Object
lub bez typu albo coś podobnego. Więc wint
porównaniustring
powyższym przykładzie nie będzie działać w tym języku, albo. C ++ ma podobne problemy, ponieważ gdy używasz czegoś takiego jak wskaźnik pustki lub jakiś inny podobny mechanizm, nadal wymaga ręcznego rozróżnienia typu w czasie kompilacji.Tak jak mówi pierwszy nagłówek ...
W przypadku współczesnych języków jest to po prostu cukier składniowy; w sposób całkowicie niezależny od języka, to coś więcej. Zwiększenie użyteczności i przydatności przeciążania metod, tak jak w powyższym przykładzie, może być dobrą cechą do dodania do istniejącego języka (jak powszechnie domagano się domyślnie w AS3) lub może też służyć jako jeden z wielu różnych fundamentalnych filarów stworzenie nowego języka proceduralnego / obiektowego.
źródło
this[chooseFunctionNameAtRandom]();
JeślichooseFunctionNameAtRandom()
powraca albo"punch"
,"kick"
lub"dodge"
, po czym można thusly wdrożyć bardzo prosty losowy element, na przykład, AI przeciwnika w grze Flash.To naprawdę zależy od twojej definicji „cukru syntaktycznego”. Spróbuję odnieść się do niektórych definicji, które przychodzą mi do głowy:
Cechą jest cukier składniowy, gdy program, który go używa, zawsze może zostać przetłumaczony na inny, który nie korzysta z tej funkcji.
Zakładamy tutaj, że istnieje prymitywny zestaw funkcji, których nie można przetłumaczyć: innymi słowy, nie ma pętli w rodzaju „można zastąpić cechę X za pomocą cechy Y” i „można zastąpić cechę Y funkcją X”. Jeśli jedna z tych dwóch prawd jest prawdą, to drugą z tych cech można wyrazić w kategoriach cech, które nie są pierwsze lub jest to cecha pierwotna.
Taki sam jak definicja 1, ale z dodatkowym wymogiem, że przetłumaczony program jest tak samo bezpieczny dla typu jak pierwszy, tzn. Po wykasowaniu nie tracisz żadnych informacji.
Definicja OP: cechą jest cukier składniowy, jeśli jego tłumaczenie nie zmienia struktury programu, a wymaga jedynie „lokalnych zmian”.
Weźmy Haskell za przykład przeciążenia. Haskell zapewnia zdefiniowane przez użytkownika przeciążenie poprzez klasy typów. Na przykład operacje
+
i*
są zdefiniowane wNum
klasie typu i można użyć dowolnego typu, który ma (kompletną) instancję takiej klasy+
. Na przykład:Jedną z dobrze znanych rzeczy na temat klas typów Haskella jest to cech że można się ich pozbyć . To znaczy możesz przetłumaczyć dowolny program, który używa klas typów w równoważnym programie, który ich nie używa.
Tłumaczenie jest dość proste:
Biorąc pod uwagę definicję klasy:
Możesz to przetłumaczyć na algebraiczny typ danych:
Tutaj
X_P_i
iX_op_i
są selektory . Czyli podana wartość typuX a
zastosowanaX_P_1
do wartości zwróci wartość zapisaną w tym polu, więc są to funkcje typuX a -> P_i a
(lubX a -> t_i
).W przypadku bardzo szorstkiej anologii można by pomyśleć o wartościach typu
X a
jako ostruct
s, a następniex
o typieX a
wyrażenia:może być postrzegane jako:
(Łatwo jest używać tylko pól pozycyjnych zamiast nazwanych, ale nazwane pola są łatwiejsze do obsługi w przykładach i unikają kodu z tablicy kotła).
Biorąc pod uwagę deklarację instancji:
Możesz przetłumaczyć go na funkcję, która podając słowniki dla
C_1 a_1, ..., C_n a_n
klas zwraca wartość słownika (tj. Wartość typuX a
) dla typuT a_1 ... a_n
.Innymi słowy powyższą instancję można przetłumaczyć na funkcję taką jak:
(Uwaga, że
n
może być0
).I faktycznie możemy to zdefiniować jako:
gdzie
op_1 = ...
sięop_m = ...
są definicje znaleźć winstance
zgłoszeniu, orazget_P_i_T
to funkcje określone przezP_i
wystąpienieT
typu (to musi istnieć z powoduP_i
S są superklasy zX
).Otrzymano wywołanie funkcji przeciążonej:
Możemy jawnie przekazać słowniki związane z ograniczeniami klasowymi i uzyskać równoważne wywołanie:
Zauważ, że ograniczenia klasowe stały się po prostu nowym argumentem. W
+
przetłumaczonym programie jest selektor, jak wyjaśniono wcześniej. Innymi słowy, przetłumaczonaadd
funkcja, biorąc pod uwagę słownik typu argumentu, najpierw „rozpakuje” rzeczywistą funkcję do obliczenia wyniku,(+) dictNum
a następnie zastosuje tę funkcję do argumentów.To tylko bardzo szybki szkic na temat całej sprawy. Jeśli jesteś zainteresowany, powinieneś przeczytać artykuły Simona Peytona Jonesa i in.
Uważam, że podobne podejście można zastosować również do przeciążania w innych językach.
Pokazuje to jednak, że jeśli twoja definicja cukru syntaktycznego to (1), to przeciążenie to cukier syntaktyczny . Ponieważ możesz się go pozbyć.
Jednak przetłumaczony program traci informacje o oryginalnym programie. Na przykład nie wymusza istnienia instancji klas nadrzędnych. (Mimo że operacje wyodrębnienia słowników rodzica muszą być nadal tego typu, możesz przekazać
undefined
lub inne wartości polimorficzne, abyś mógł zbudować wartośćX y
bez budowania wartości dlaP_i y
, więc tłumaczenie nie straci wszystkiego rodzaj bezpieczeństwa). Dlatego nie jest to cukier syntacti według (2)Co do (3). Nie wiem, czy odpowiedź powinna brzmieć „tak” czy „nie”.
Powiedziałbym „nie”, ponieważ na przykład deklaracja instancji staje się definicją funkcji. Funkcje, które są przeciążone, otrzymują nowy parametr (co oznacza, że zmienia zarówno definicję, jak i wszystkie wywołania).
Powiedziałbym „tak”, ponieważ dwa programy wciąż mapują jeden na jeden, więc „struktura” nie uległa tak dużym zmianom.
To powiedziawszy, powiedziałbym, że pragmatyczne korzyści wynikające z przeciążenia są tak duże, że użycie terminu „uwłaczającego”, takiego jak „cukier składniowy”, nie wydaje się prawidłowe.
Możesz przetłumaczyć całą składnię Haskell na bardzo prosty język Core (co jest faktycznie wykonywane podczas kompilacji), więc większość składni Haskell może być postrzegana jako „cukier składniowy” dla czegoś, co jest tylko rachunkiem lambda i nieco nowymi konstrukcjami. Możemy jednak zgodzić się, że programy Haskell są znacznie łatwiejsze w obsłudze i bardzo zwięzłe, podczas gdy przetłumaczone programy są trudniejsze do odczytania lub przemyślenia.
źródło
Jeśli wysyłka jest rozstrzygana w czasie kompilacji, w zależności tylko od statycznego typu wyrażenia argumentu, możesz z pewnością argumentować, że jest to „cukier składniowy” zastępujący dwie różne metody różnymi nazwami, pod warunkiem, że programista „zna” typ statyczny i może po prostu użyć właściwej nazwy metody zamiast przeciążonej nazwy. Tak też jest forma statycznego polimorfizmu, ale w tej ograniczonej formie zwykle nie jest bardzo silna.
Oczywiście uciążliwością byłaby zmiana nazw wywoływanych metod przy każdej zmianie typu zmiennej, ale na przykład w języku C jest to uważane za uciążliwe, więc C nie ma przeciążenia funkcji (chociaż ma teraz ogólne makra).
W szablonach C ++ iw każdym języku, który dokonuje nietrywialnej dedukcji typu statycznego, tak naprawdę nie można argumentować, że jest to „cukier syntaktyczny”, chyba że argumentujesz również, że dedukcja typu statycznego to „cukier syntaktyczny”. Uciążliwym byłoby nie mieć szablonów, aw kontekście C ++ byłoby to „niedogodne do opanowania”, ponieważ są one tak idiomatyczne dla języka i jego standardowych bibliotek. Tak więc w C ++ jest to coś więcej niż miły pomocnik, jest ważne dla stylu języka, więc myślę, że musisz to nazwać bardziej niż „cukier składniowy”.
W Javie możesz uznać to za coś więcej niż wygodę, biorąc pod uwagę na przykład liczbę przeciążeń
PrintStream.print
iPrintStream.println
. Ale jest tyleDataInputStream.readX
metod, ponieważ Java nie przeciąża typu zwracanego, więc w pewnym sensie jest to tylko dla wygody. Wszystkie dotyczą prymitywnych typów.Nie pamiętam, co się dzieje w Javie, jeśli mam zajęcia
A
iB
wydłużanieO
, ja przeciążać metodyfoo(O)
,foo(A)
afoo(B)
, a następnie w ogólnym z<T extends O>
zadzwonięfoo(t)
gdziet
jest instancjąT
. W przypadku, gdyT
jestA
uzyskać wysyłkę na podstawie przeciążenia czy jest to jakby zadzwoniłemfoo(O)
?Jeśli to pierwsze, to przeciążenia metody Java są lepsze niż cukier w taki sam sposób, jak przeciążenia C ++. Używając twojej definicji, przypuszczam, że w Javie mógłbym lokalnie napisać serię kontroli typu (co byłoby kruche, ponieważ nowe przeciążenia
foo
wymagałyby dodatkowych kontroli). Oprócz zaakceptowania tej niestabilności nie mogę dokonać lokalnej zmiany w witrynie wywoływania, aby zrobić to dobrze, zamiast tego musiałbym zrezygnować z pisania ogólnego kodu. Twierdziłbym, że zapobieganie rozdętemu kodowi może być cukrem składniowym, ale zapobieganie niestabilnemu kodowi to coś więcej. Z tego powodu statyczny polimorfizm ogólnie jest czymś więcej niż tylko cukrem syntaktycznym. Sytuacja w danym języku może być inna, w zależności od tego, jak daleko język pozwala przejść, „nie znając” typu statycznego.źródło
T:Animal
to jest rodzajSiameseCat
i istniejące przeciążenia sąCat Foo(Animal)
,SiameseCat Foo(Cat)
iAnimal Foo(SiameseCat)
, co przeciążenie powinien być wybrany, jeśliT
jestSiameseCat
?long foo=Math.round(bar*1.0001)*5
zostanie zmieniona nalong foo=Math.round(bar)*5
. Jak wpłynęłoby to na semantykę, gdyby byłabar
równa np. 123456789L?long
celudouble
.double
?Wygląda na to, że „cukier syntaktyczny” brzmi obraźliwie, jak bezużyteczny lub niepoważny. Dlatego pytanie wywołuje wiele negatywnych odpowiedzi.
Ale masz rację, przeciążanie metod nie dodaje żadnej funkcji do języka, z wyjątkiem możliwości użycia tej samej nazwy dla różnych metod. Możesz jawnie określić typ parametru, program będzie nadal działał tak samo.
To samo dotyczy nazw pakietów. Łańcuch jest po prostu cukrem syntaktycznym dla java.lang.String.
W rzeczywistości metoda taka
w klasie MyClass należy nazwać „my_package_MyClass_fun_int_java_lang_String”. Oznaczałoby to jednoznaczną identyfikację metody. (JVM robi coś takiego wewnętrznie). Ale nie chcesz tego pisać. Właśnie dlatego kompilator pozwoli ci pisać zabawnie (1, „jeden”) i określa, którą to metodę.
Jest jednak jedna rzecz, którą można zrobić z przeciążeniem: jeśli przeciążymy metodę z taką samą liczbą argumentów, kompilator automatycznie ustali, która wersja najlepiej odpowiada argumentowi podanemu przez dopasowanie argumentów, nie tylko z równymi typami, ale także gdzie dany argument jest podklasą zadeklarowanego argumentu.
Jeśli masz dwie przeciążone procedury
nie musisz wiedzieć, że istnieje konkretna wersja procedury Daty. addParameter („hello”, „world) wywoła pierwszą wersję, addParameter („ teraz ”, nowa data ()) wywoła drugą.
Oczywiście powinieneś unikać przeciążania metody inną metodą, która robi coś zupełnie innego.
źródło
Co ciekawe, odpowiedź na to pytanie zależy od języka.
W szczególności istnieje interakcja między przeciążeniem a programowaniem ogólnym (*), a w zależności od sposobu realizacji programowania ogólnego może to być po prostu cukier składniowy (Rust) lub absolutnie niezbędny (C ++).
Oznacza to, że gdy programowanie ogólne jest implementowane z wyraźnymi interfejsami (w Rust lub Haskell byłyby to klasy typu), wówczas przeciążenie jest po prostu cukrem syntaktycznym; lub może nawet nie być częścią języka.
Z drugiej strony, gdy programowanie ogólne jest wdrażane za pomocą pisania kaczego (dynamicznego lub statycznego), nazwa metody jest istotną umową, a zatem przeciążenie jest obowiązkowe, aby system mógł działać.
(*) Używany w sensie jednokrotnego napisania metody, aby operować na różnych typach w jednolity sposób.
źródło
W niektórych językach jest to bez wątpienia jedynie cukier syntaktyczny. Jednak to, co jest cukrem, zależy od twojego punktu widzenia. Zostawię tę dyskusję na później w tej odpowiedzi.
Na razie chciałbym zauważyć, że w niektórych językach z pewnością nie jest to cukier składniowy. Przynajmniej nie bez konieczności stosowania zupełnie innej logiki / algorytmu do implementacji tego samego. To tak, jakby twierdzić, że rekurencja jest cukrem składniowym (tak jest, ponieważ można napisać cały algorytm rekurencyjny za pomocą pętli i stosu).
Jeden przykład bardzo trudnego do zastąpienia użycia pochodzi z języka, który jak na ironię nie nazywa tej funkcji „przeciążeniem funkcji”. Zamiast tego nazywa się to „dopasowywaniem wzorców” (co może być postrzegane jako nadzbiór przeciążania, ponieważ możemy przeciążać nie tylko typy, ale wartości).
Oto klasyczna naiwna implementacja funkcji Fibonacciego w Haskell:
Prawdopodobnie te trzy funkcje mogą być zastąpione przez if / else, ponieważ jest to zwykle wykonywane w każdym innym języku. Ale to zasadniczo czyni całkowicie prostą definicję:
znacznie bardziej chaotyczny i nie wyraża bezpośrednio matematycznego pojęcia sekwencji Fibonacciego.
Czasami może to być cukier składniowy, jeśli jedynym zastosowaniem jest umożliwienie wywołania funkcji z różnymi argumentami. Ale czasami jest to o wiele bardziej fundamentalne.
Teraz porozmawiajmy o tym, do czego przeciążenie operatora może być cukrem. Zidentyfikowałeś jeden przypadek użycia - można go użyć do implementacji podobnych funkcji, które przyjmują różne argumenty. Więc:
można alternatywnie zaimplementować jako:
lub nawet:
Ale przeciążenie operatora może być również cukrem do implementacji opcjonalnych argumentów (niektóre języki mają przeciążenie operatora, ale nie opcjonalne argumenty):
może być wykorzystany do wdrożenia:
W takim języku (Google „Ferite language”) usunięcie przeciążenia operatora drastycznie usuwa jedną funkcję - opcjonalne argumenty. Przyznane w językach, w których obie funkcje (c ++) usuwają jedną lub drugą, nie będą miały żadnego efektu netto, ponieważ można użyć dowolnego z nich do implementacji opcjonalnych argumentów.
źródło
data PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfo
który można dopasować do wzorca konstruktorów typów.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
. Mam nadzieję, że ten komentarz jest nieco zrozumiały.Myślę, że jest to prosty cukier syntaktyczny w większości języków (przynajmniej wszystko, co wiem ...), ponieważ wszystkie wymagają jednoznacznego wywołania funkcji w czasie kompilacji. A kompilator po prostu zastępuje wywołanie funkcji wyraźnym wskaźnikiem do odpowiedniej sygnatury implementacji.
Przykład w Javie:
W końcu można go całkowicie zastąpić prostym makro-kompilatorem z wyszukiwaniem i zamienianiem, zastępując przeciążony menedżer funkcji mangle_String i mangle_int - ponieważ lista argumentów jest częścią ostatecznego identyfikatora funkcji, to praktycznie tak się dzieje -> i dlatego jest to tylko cukier syntaktyczny.
Teraz, jeśli istnieje język, w którym funkcja jest naprawdę określana w czasie wykonywania, tak jak w przypadku przesłoniętych metod w obiektach, byłoby inaczej. Ale nie sądzę, aby istniał taki język, ponieważ metoda. Przeciążenie jest podatne na dwuznaczności, których kompilator nie może rozwiązać i które muszą być obsługiwane przez programistę z jawną obsadą. Nie można tego zrobić w czasie wykonywania.
źródło
W typie Java informacje są kompilowane, a które z przeciążeń jest wywoływane, decyduje w czasie kompilacji.
Poniżej znajduje się fragment
sun.misc.Unsafe
kodu (narzędzie dla Atomics), jak pokazano w edytorze plików klas Eclipse.jak widać informacje o typie wywoływanej metody (linia 4) są zawarte w wywołaniu.
Oznacza to, że można utworzyć kompilator Java, który pobiera informacje o typie. Na przykład przy użyciu takiej notacji powyższym źródłem byłoby:
a obsada na długi byłaby opcjonalna.
W innych statycznie skompilowanych językach zobaczysz podobną konfigurację, w której kompilator zdecyduje, które przeciążenie zostanie wywołane w zależności od typu i uwzględni go w powiązaniu / wywołaniu.
Wyjątkiem są biblioteki dynamiczne C, w których nie podano informacji o typie, a próba utworzenia przeciążonej funkcji spowoduje, że linker narzeka.
źródło