Multimethod ( anglicky multimethod ) nebo multiple dispatch ( anglicky multiple dispatch ) je mechanismus v programovacích jazycích, který umožňuje vybrat jednu z několika funkcí v závislosti na dynamických typech nebo hodnotách argumentů (například přetížení metody v některých programovacích jazycích) . Jedná se o rozšíření single dispatch ( virtuální funkce ), kde se výběr metody provádí dynamicky na základě skutečného typu objektu, na kterém byla metoda volána. Vícenásobné odeslání zobecňuje dynamické odeslání pro případy se dvěma nebo více objekty.
Multimetody jsou explicitně podporovány "Common Lisp Object System" ( CLOS ).
Vývojáři programů mají tendenci seskupovat zdrojový kód do pojmenovaných bloků nazývaných volání, procedury, podprogramy , funkce nebo metody. Kód funkce se provádí jejím voláním, které spočívá ve spuštění části kódu označené jejím jménem. V tomto případě je řízení dočasně přeneseno na volanou funkci; po dokončení této funkce se řízení obvykle přenese zpět do instrukce následující po volání funkce.
Názvy funkcí se obvykle volí tak, aby popisovaly jejich účel. Někdy je nutné pojmenovat několik funkcí stejným názvem, obvykle proto, že plní koncepčně podobné úkoly, ale pracují s různými typy vstupních dat. V takových případech jméno funkce v místě jejího volání nestačí k určení bloku kódu, který má být volán. Kromě názvu se v tomto případě pro výběr konkrétní implementace funkce používá také počet a typ argumentů volané funkce.
V tradičnějších objektově orientovaných programovacích jazycích s jedním odesláním, když je volána metoda (odeslání zprávy ve Smalltalku , volání členské funkce v C++ ), jeden z jejích argumentů se zpracuje zvláštním způsobem a použije se k určení, který z ( musí být voláno potenciálně mnoho) metod s tímto názvem. V mnoha jazycích je tento speciální argument uveden syntakticky, například v řadě programovacích jazyků je speciální argument umístěn před tečkou při volání metody:
speciální metoda (jiné, argumenty, zde)takže lion.sound() bude vydávat řev a sparrow.sound() bude vydávat cvrlikání.
Naproti tomu v jazycích s vícenásobným odesláním je zvolená metoda jednoduše metoda, jejíž argumenty odpovídají počtu a typu argumentů ve volání funkce. Není zde žádný speciální argument, který by „vlastnil“ funkci nebo metodu, na kterou odkazuje konkrétní volání.
Common Lisp Object System (CLOS) je jednou z prvních a dobře známých implementací vícenásobného odeslání.
Při práci s jazyky, ve kterých se datové typy rozlišují v době kompilace, může v době kompilace dojít k výběru mezi dostupnými možnostmi funkcí. Vytváření takových možností alternativních funkcí, ze kterých si můžete vybrat v době kompilace, se běžně nazývá přetížení funkcí .
V programovacích jazycích, které určují datové typy za běhu (pozdní vazba), musí výběr mezi možnostmi funkcí probíhat za běhu na základě dynamicky určených typů argumentů funkcí. Funkce, jejichž alternativní implementace jsou zvoleny tímto způsobem, se běžně označují jako multimetody.
S dynamickým odesíláním volání funkcí jsou spojeny určité provozní náklady. V některých jazycích může být rozdíl mezi přetížením funkcí a multimetodami rozmazaný, přičemž kompilátor určí, zda lze volbu volané funkce provést v době kompilace, nebo je vyžadováno pomalejší odesílání za běhu.
Aby bylo možné posoudit, jak často se vícenásobný dispečink v praxi používá, Muschevici et al [1] zkoumali aplikace, které využívají dynamický dispečink. Analyzovali devět aplikací, většinou kompilátorů, napsaných v šesti různých programovacích jazycích: Common Lisp Object System , Dylan , Cecil, MultiJava, Diesel a Nice. Výsledky ukazují, že 13 % až 32 % generických funkcí používá dynamické psaní s jedním argumentem, zatímco 2,7 % až 6,5 % funkcí používá dynamické psaní s více argumenty. Zbývajících 65 % až 93 % generických funkcí má jednu specifickou metodu (přetíženou), a proto se neuvažovalo o použití dynamického typování jejich argumentů. Studie navíc uvádí, že 2 až 20 % generických funkcí mělo dvě a 3 až 6 % mělo tři své specifické implementace. Podíl funkcí s velkým počtem konkrétních implementací rychle klesal.
Teorii multi-call jazyků poprvé rozvinul Castagna et al., když definoval model přetížených funkcí s pozdní vazbou [2] [3] . Vznikla tak první formalizace problému kovariance a protivariace objektově orientovaných programovacích jazyků [4] a řešení problému binárních metod [5] .
Abychom lépe porozuměli rozdílu mezi multimetodami a jednoduchým odesláním, lze demonstrovat následující příklad. Představte si hru, ve které jsou spolu s řadou dalších objektů asteroidy a vesmírné lodě. Když se jakékoli dva objekty srazí, program musí zvolit určitý algoritmus akcí podle toho, co se s čím srazilo.
V jazyce s více metodami, jako je Common Lisp , by kód vypadal takto:
( defgenerická kolize ( x y )) ( defmethod se srazí (( x asteroid ) ( y asteroid )) ;; asteroid se srazí s asteroidem ) ( defmethod se srazí (( x asteroid ) ( y vesmírná loď )) ;;asteroid se srazí s vesmírnou lodí ) ( defmethod se srazí (( x kosmická loď ) ( y asteroid )) ;;kosmická loď se srazí s asteroidem ) ( defmethod se srazí (( x vesmírná loď ) ( y vesmírná loď )) ;; vesmírná loď se srazí s vesmírnou lodí )a podobně i u jiných metod. Explicitní kontrola a "dynamické přehazování" se zde nepoužívá.
S vícenásobným odesláním se tradiční přístup definování metod ve třídách a jejich ukládání do objektů stává méně atraktivní, protože každá kolidovaná metoda odkazuje na dvě různé třídy namísto jedné. Speciální syntaxe pro volání metody tedy obecně zmizí, takže volání metody vypadá přesně jako normální volání funkce a metody nejsou seskupeny podle třídy, ale do generických funkcí .
Raku, stejně jako předchozí verze, využívá osvědčené nápady z jiných jazyků a typových systémů, aby nabídl přesvědčivé výhody v analýze kódu na straně kompilátoru a výkonné sémantice prostřednictvím vícenásobného odeslání.
Má jak multimetody, tak multisubrutiny. Protože většina příkazů jsou podprogramy, existují také příkazy s vícenásobným odesláním.
Spolu s obvyklými omezeními typu má také omezení typu „kde“, což umožňuje vytvářet velmi specializované podprogramy.
podmnožina Skutečná hmotnost kde 0 ^ ..^ Inf ; role Stellar-Object { has Mass $.hmotnost je vyžadována ; název metody () vrací Str {...}; } class Asteroid dělá Stellar-Object { název metody () { 'asteroid' } } class Vesmírná loď dělá Stellar-Object { has Str $.name = 'nějaká nejmenovaná vesmírná loď' ; } my Str @destroyed = < vymazáno zničeno zničeno >; my Str @damaged = " poškozený 'kolidoval s' 'byl poškozen' "; # K operátorům numerického porovnávání přidáváme více kandidátů, protože je porovnáváme numericky, # ale nedává smysl, aby byly objekty nuceny k typu Numeric. # (Pokud by donucovali, nemuseli bychom nutně tyto operátory přidávat.) # Stejným způsobem jsme mohli definovat také zcela nové operátory. multi sub infix: " <=> " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . hmotnost <=> $b . mass } multi sub infix: " < " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . hmotnost < $b . mass } multi sub infix: " > " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . hmotnost > $b . mass } multi sub infix: " == " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . hmotnost == $b . hmotnost } # Definujte nový multidispečer a přidejte do parametrů některá omezení typu. # Pokud bychom to nedefinovali, dostali bychom obecný, který by neměl žádná omezení. proto sub collide ( Stellar-Object:D $, Stellar-Object:D $ ) {*} # Není třeba opakovat typy, protože jsou stejné jako prototyp. # Omezení 'kde' se technicky vztahuje pouze na $b, nikoli na celý podpis. # Všimněte si, že omezení 'kde' používá kandidáta na operátora `<`, který jsme přidali dříve. multi sub collide ( $a , $b kde $a < $b ) { say "$a.name() byl @destroyed.pick() by $b.name()" ; } multi sub collide ( $a , $b kde $a > $b ) { # přeposlat předchozímu kandidátovi s argumenty prohozenými stejnými s $b , $a ; } # Toto musí být po prvních dvou, protože ostatní # mají omezení 'kde', která se kontrolují v # pořadí, v jakém byly subs zapsány. (Tohle by se vždy shodovalo. ) multi sub collide ( $a , $b ){ # randomize order my ( $n1 , $n2 ) = ( $a . name , $b . name ). vybrat (*); řekněte "$n1 @damaged.pick() $n2" ; } # Následující dva kandidáti mohou být kdekoli po proto, # protože mají specializovanější typy než předchozí tři. # Pokud mají lodě nestejnou hmotnost, bude místo toho povolán jeden z prvních dvou kandidátů. multi sub collide ( Spaceship $a , Spaceship $b where $a == $b ){ my ( $n1 , $n2 ) = ( $a . name , $b . name ). vybrat (*); řekněte "$n1 se srazil s $n2 a obě lodě byly " , ( @destroyed . pick , 'left poškozeno' ). vybrat ; } # Atributy můžete rozbalit do proměnných v rámci podpisu. # Dokonce na ně můžete mít omezení `(:hmotnost($a) kde 10)`. multi sub collide ( Asteroid $ (: mass ( $a )), Asteroid $ (: mass ( $b )) ){ say "dva asteroidy se srazily a spojily se do jednoho většího asteroidu o hmotnosti { $a + $b }" ; } moje kosmická loď $Enterprise .= new (: mass ( 1 ),: name ( 'The Enterprise' )); srazit asteroid . new (: hmotnost ( .1 )), $Enterprise ; srazit $Enterprise , kosmická loď . nový (: hmotnost ( .1 )); srazit $Enterprise , Asteroid . nový (: hmotnost ( 1 )); srazit $Enterprise , kosmická loď . nový (: hmotnost ( 1 )); srazit asteroid . nový (: hmotnost ( 10 )), Asteroid . nový (: hmotnost ( 5 ));V jazycích, které nepodporují vícenásobné odeslání na úrovni syntaxe, jako je Python , je obecně možné použít vícenásobné odeslání pomocí knihoven rozšíření. Například modul multimethods.py [6] implementuje multimetody ve stylu CLOS v Pythonu beze změny syntaxe nebo klíčových slov jazyka.
from multimethods import Odeslání z game_objects import Asteroid , Spaceship from game_behaviors import ASFunc , SSFunc , SAFunc collide = Dispatch () collide . add_rule (( Asteroid , Spaceship ), ASFunc ) se srazí . add_rule (( Spaceship , Spaceship ), SSFunc ) collide . add_rule (( Kosmická loď , Asteroid ), SAFunc ) def AAFunc ( a , b ): """Chování při dopadu asteroidu na asteroid""" # ...definovat nové chování... kolidovat . add_rule (( Asteroid , Asteroid ), AAFunc ) # ...později... srazit ( věc1 , věc2 )Funkčně je to velmi podobné příkladu CLOS, ale syntaxe se řídí standardní syntaxí Pythonu.
Pomocí dekorátorů Pythonu 2.4 napsal Guido van Rossum příklad implementace multimetod [7] se zjednodušenou syntaxí:
@multimethod ( Asteroid , Asteroid ) def collide ( a , b ): """Chování, když asteroid narazí na asteroid""" # ...definovat nové chování... @multimethod ( Asteroid , Spaceship ) def collide ( a , b ) : """Chování při dopadu asteroidu na vesmírnou loď""" # ...definovat nové chování... # ... definovat další multimetodová pravidla ...a poté je definována multimetoda dekoratérů.
Balíček PEAK-Rules implementuje vícenásobné odeslání se syntaxí podobnou výše uvedenému příkladu. [osm]
V jazycích, které mají pouze jedno odeslání, jako je Java , by tento kód vypadal takto ( tento problém však může pomoci vyřešit vzor návštěvníka ):
/* Příklad použití porovnání typu běhu pomocí operátoru "instanceof" Java */ interface Collideable { /* Vytvoření třídy by nezměnilo demonstraci. */ void collideWith ( Collideable other ); } class Asteroid implementuje Collideable { public void collideWith ( Collideable other ) { if ( other instanceof Asteroid ) { // Zvládne kolizi Asteroid-Asteroid. } else if ( other instanceof Spaceship ) { // Řeší kolizi asteroid-vesmírná loď. } } } class Kosmická loď implementuje Collideable { public void collideWith ( Collideable other ) { if ( other instanceof Asteroid ) { // Zvládne kolizi mezi kosmickou lodí a asteroidem. } else if ( other instanceof Spaceship ) { // Ošetření kolize mezi kosmickou lodí a kosmickou lodí. } } }C nemá dynamické odesílání, takže musí být implementováno ručně v té či oné podobě. K identifikaci podtypu objektu se často používá výčet. Dynamické odesílání lze implementovat vyhledáním této hodnoty v tabulce větví ukazatelů funkcí. Zde je jednoduchý příklad v C:
typedef void ( * CollisionCase )(); void kolize_AA () { /* Zpracování kolize asteroid-asteroid */ }; void kolize_AS () { /* Zpracování kolize Asteroid-Loď */ }; neplatná kolize_SA () { /* Zpracování kolize mezi lodí a asteroidem */ }; void konflikt_SS () { /* řešení kolize mezi lodí */ }; typedef enum { asteroid = 0 _ vesmírná loď , num_thing_types /* není typ objektu, používá se k nalezení počtu objektů */ } Věc ; CollisionCase kolizeCases [ num_thing_types ][ num_thing_types ] = { { & kolize_AA , & kolize_AS }, { & kolize_SA , & kolize_SS } }; void collide ( věc a , věc b ) { ( * kolizeCases [ a ][ b ])(); } int main () { srazit ( kosmická loď , asteroid ); }Od roku 2015 podporuje C++ pouze jedno odeslání, i když se zvažuje podpora vícenásobného odeslání. [9] Řešení tohoto omezení jsou podobná: buď pomocí vzoru návštěvníka, nebo dynamického castingu:
// Příklad použití porovnání typu běhu přes dynamic_cast struct Thing { virtuální void collideWith ( Thing & other ) = 0 ; }; struct Asteroid : Thing { void collideWith ( Thing & other ) { // dynamic_cast na typ ukazatele vrátí NULL, pokud se cast nezdaří // (dynamic_cast do referenčního typu by vyvolalo výjimku při selhání) if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( & other )) { // zvládnout kolizi asteroid-asteroid } else if ( Kosmická loď * kosmická loď = dynamic_cast < Vesmírná loď *> ( & další )) { // zvládnout kolizi asteroid-vesmírná loď } else { // výchozí řešení kolizí zde } } }; struct Spaceship : Thing { void collideWith ( Thing & other ) { if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( a další )) { // zvládnout kolizi vesmírná loď-asteroid } else if ( Kosmická loď * vesmírná loď = dynamic_cast < Vesmírná loď *> ( & další )) { // zvládnout kolizi kosmické lodi a kosmické lodi } else { // výchozí řešení kolizí zde } } };nebo vyhledávací tabulky pro ukazatele na metody:
#include <typeinfo> #include <mapa_neuspořádané> typedef unsigned uint4 ; typedef unsigned long long uint8 ; třída Věc { chráněno : Věc ( const uint4 cid ) : tid ( cid ) {} const uint4 tid ; // zadejte id typedef void ( Thing ::* CollisionHandler )( Thing & other ); typedef std :: unordered_map < uint8 , CollisionHandler > CollisionHandlerMap ; static void addHandler ( const uint4 id1 , const uint4 id2 , const CollisionHandler handler ) { kolizní případy . insert ( CollisionHandlerMap :: value_type ( key ( id1 , id2 ), handler )); } statický klíč uint8 ( const uint4 id1 , const uint4 id2 ) { return uint8 ( id1 ) << 32 | id2 ; } statický CollisionHandlerMap kolizeCases ; veřejnost : void collideWith ( Thing & other ) { CollisionHandlerMap :: handler const_iterator = kolizeCases . najít ( klíč ( tid , other . tid )); if ( handler != kolizeCases . end ()) { ( this ->* handler -> second )( other ); // volání ukazatele na metodu } else { // výchozí zpracování kolizí } } }; class Asteroid : public Thing { void asteroid_collision ( Thing & other ) { /*zvládnutí kolize asteroid-asteroid*/ } void spaceship_collision ( Thing & other ) { /*zvládnout kolizi asteroid-vesmírná loď*/ } veřejnost : Asteroid () : Věc ( cid ) {} static void initCases (); static const uint4 cid ; }; class Spaceship : public Thing { void asteroid_collision ( Thing & other ) { /*vypořádat se s kolizí vesmírné lodi a asteroidu*/ } void spaceship_collision ( Thing & other ) { /*řešit kolizi mezi vesmírnou lodí a vesmírnou lodí*/ } veřejnost : Vesmírná loď () : Věc ( cid ) {} static void initCases (); static const uint4 cid ; // id třídy }; Věc :: CollisionHandlerMap Věc :: kolizeCases ; const uint4 Asteroid :: cid = typeid ( Asteroid ). hash_code (); const uint4 Vesmírná loď :: cid = typeid ( Vesmírná loď ). hash_code (); void Asteroid::initCases () { addHandler ( cid , cid , ( CollisionHandler ) & Asteroid :: asteroid_collision ); addHandler ( cid , Spaceship :: cid , ( CollisionHandler ) & Asteroid :: spaceship_collision ); } void Spaceship::initCases () { addHandler ( cid , Asteroid :: cid , ( CollisionHandler ) & Spaceship :: asteroid_collision ); addHandler ( cid , cid , ( CollisionHandler ) & Spaceship :: spaceship_collision ); } int main () { Asteroid :: initCases (); kosmická loď :: initCases (); asteroid a1 , a2 ; Kosmická loď s1 , s2 ; a1 . collideWith ( a2 ); a1 . collideWith ( s1 ); s1 . collideWith ( s2 ); s1 . collideWith ( al ); }Knihovna yomm11 [10] vám umožňuje automatizovat tento přístup.
Stroustrup ve své knize The Design and Evolution of C++ zmiňuje, že se mu líbí koncept multimetod a že zvažoval jejich implementaci v C++, ale tvrdí, že nenašel příklad efektivní (ve srovnání) s virtuálními funkcemi k implementaci. a vyřešit některé možné problémy typu nejednoznačnosti. Dále tvrdí, že i když by bylo hezké implementovat podporu pro tento koncept, lze jej aproximovat dvojitým odesláním nebo typově založenou vyhledávací tabulkou, jak je popsáno v příkladu C/C++ výše, takže tento úkol má při vývoji nízkou prioritu. budoucích verzí jazyka. [jedenáct]
Podpora multimetod v jiných jazycích prostřednictvím rozšíření:
Třídy víceparametrového typu v Haskell a Scala lze také použít k emulaci multimetod.