Multimetode

Den aktuelle version af siden er endnu ikke blevet gennemgået af erfarne bidragydere og kan afvige væsentligt fra den version , der blev gennemgået den 5. januar 2015; checks kræver 29 redigeringer .

Multimethod ( engelsk  multimethod ) eller multiple dispatch ( engelsk  multiple dispatch ) er en mekanisme i programmeringssprog, der giver dig mulighed for at vælge en af ​​flere funktioner afhængigt af dynamiske typer eller argumentværdier (for eksempel metodeoverbelastning i nogle programmeringssprog) . Det er en udvidelse af enkelt afsendelse ( virtuelle funktioner ), hvor metodevalg udføres dynamisk baseret på den faktiske type af objektet, som metoden blev kaldt. Multiple afsendelse generaliserer dynamisk afsendelse for sager med to eller flere objekter.

Multimetoder understøttes eksplicit af "Common Lisp Object System" ( CLOS ).

Grundlæggende om afsendelse

Programudviklere har en tendens til at gruppere kildekoden i navngivne blokke kaldet opkald, procedurer, underrutiner , funktioner eller metoder. Koden til en funktion udføres ved at kalde den, hvilket består i at udføre det stykke kode, der er angivet med dens navn. I dette tilfælde overføres kontrollen midlertidigt til den kaldte funktion; når denne funktion er fuldført, overføres kontrollen normalt tilbage til instruktionen efter funktionskaldet.

Funktionsnavne vælges normalt for at beskrive deres formål. Nogle gange er det nødvendigt at navngive flere funktioner ved samme navn, normalt fordi de udfører konceptuelt ens opgaver, men arbejder med forskellige typer inputdata. I sådanne tilfælde er navnet på funktionen på stedet for dens opkald ikke nok til at bestemme den kodeblok, der skal kaldes. Ud over navnet bruges i dette tilfælde antallet og typen af ​​argumenter for den kaldte funktion også til at vælge en specifik implementering af funktionen.

I mere traditionelle enkelt-afsendelse objektorienterede programmeringssprog, når en metode kaldes (sende en besked i Smalltalk , kalder en medlemsfunktion i C++ ), behandles et af dets argumenter på en speciel måde og bruges til at bestemme hvilken af ​​( potentielt mange) metoder med det navn skal kaldes. På mange sprog er dette specielle argument angivet syntaktisk, for eksempel i en række programmeringssprog placeres et særligt argument før prikken, når en metode kaldes:

speciel metode (andet, argumenter, her)

så lion.sound() vil producere et brøl, og sparrow.sound() vil producere et kvidren.

I modsætning hertil er den valgte metode i sprog med flere afsendelser simpelthen den metode, hvis argumenter matcher antallet og typen af ​​argumenter i funktionskaldet. Der er ikke noget særligt argument her, der "ejer" funktionen eller metoden, der refereres til af et bestemt kald.

Common Lisp Object System (CLOS) er en af ​​de første og velkendte implementeringer af multipel afsendelse.

Datatyper

Når du arbejder med sprog, hvor datatyper skelnes på kompileringstidspunktet, kan valg blandt de tilgængelige funktionsmuligheder ske på kompileringstidspunktet. Oprettelse af sådanne alternative funktionsmuligheder at vælge imellem på kompileringstidspunktet omtales almindeligvis som funktionsoverbelastning .

I programmeringssprog, der bestemmer datatyper ved kørsel (sen binding), skal udvælgelse blandt funktionsmuligheder ske under kørsel baseret på dynamisk bestemte funktionsargumenttyper. Funktioner, hvis alternative implementeringer er valgt på denne måde, kaldes almindeligvis multimetoder.

Der er nogle driftsomkostninger forbundet med dynamisk afsendelse af funktionskald. På nogle sprog kan skelnen mellem funktionsoverbelastning og multimetoder være sløret, hvor compileren bestemmer, om valget af den kaldte funktion kan foretages på kompileringstidspunktet, eller om der kræves langsommere afsendelse under kørsel.

Praktisk brug

For at vurdere, hvor ofte multiple dispatching bruges i praksis, undersøgte Muschevici et al . [1] applikationer, der bruger dynamisk dispatching. De analyserede ni applikationer, for det meste compilere, skrevet på seks forskellige programmeringssprog: Common Lisp Object System , Dylan , Cecil, MultiJava, Diesel og Nice. Resultaterne viser, at 13 % til 32 % af de generiske funktioner bruger dynamisk skrivning med et enkelt argument, mens 2,7 % til 6,5 % af funktionerne bruger dynamisk skrivning med flere argumenter. De resterende 65%-93% af generiske funktioner har én specifik metode (overbelastet), og blev derfor ikke anset for at bruge dynamisk typning af deres argumenter. Derudover rapporterer undersøgelsen, at mellem 2% og 20% ​​af generiske funktioner havde to, og 3%-6% havde tre af deres specifikke implementeringer. Andelen af ​​funktioner med et stort antal konkrete implementeringer var hurtigt faldende.

Teori

Teorien om multiopkaldssprog blev først udviklet af Castagna et al., ved at definere en model for overbelastede funktioner med sen binding [2] [3] . Dette gav den første formalisering af problemet med kovarians og modvariation af objektorienterede programmeringssprog [4] og løsningen af ​​problemet med binære metoder [5] .

Eksempel

For bedre at forstå forskellen mellem multimetoder og enkelt forsendelse kan følgende eksempel demonstreres. Forestil dig et spil, hvor der sammen med en række andre objekter er asteroider og rumskibe. Når to objekter kolliderer, skal programmet vælge en bestemt handlingsalgoritme, afhængig af hvad der kolliderede med hvad.

Common Lisp

I et multi-metode sprog som Common Lisp ville koden se sådan ud:

( defgenerisk sammenstød ( x y )) ( defmetode kolliderer (( x asteroide ) ( y asteroide )) ;; asteroide kolliderer med asteroide ) ( defmetode kolliderer (( x asteroide ) ( y rumskib )) ;; asteroide kolliderer med rumskib ) ( defmetode kolliderer (( x rumskib ) ( y asteroide )) ;; rumskib kolliderer med en asteroide ) ( defmetode kolliderer (( x rumskib ) ( y rumskib )) ;; rumskib kolliderer med rumskib )

og tilsvarende for andre metoder. Eksplicit kontrol og "dynamisk casting" bruges ikke her. 

Med multiple afsendelser bliver den traditionelle tilgang med at definere metoder i klasser og gemme dem i objekter mindre attraktiv, da hver kollider-med-metode refererer til to forskellige klasser i stedet for én. Således forsvinder den specielle syntaks for at kalde en metode generelt, så et metodekald ser nøjagtigt ud som et normalt funktionskald, og metoder grupperes ikke efter klasse, men i generiske funktioner .

Raku

Raku bruger, ligesom tidligere versioner, gennemprøvede ideer fra andre sprog og typesystemer til at tilbyde overbevisende fordele inden for kodeanalyse på kompilatorsiden og kraftfuld semantik gennem flere udsendelser.

Den har både multimetoder og multisubrutiner. Da de fleste udsagn er underrutiner, er der også udsagn med flere udsendelser.

Sammen med de sædvanlige type begrænsninger har den også "hvor" type begrænsninger, som giver dig mulighed for at oprette meget specialiserede underrutiner.

delmængde Realmasse hvor 0 ^ .. ^ Inf ; rolle Stellar-Object { har masse $.masse er påkrævet ; metodenavn ( ) returnerer Str {...}; } klasse Asteroid gør Stellar-Object { metodenavn ( ) { 'en asteroide' } } klasse Rumskib gør Stellar-Object { has Str $.name = 'noget unavngivet rumskib' ; } min Str @ ødelagt = < udslettet ødelagt ødelagt > ; my Str @damaged = "  beskadiget 'kolliderede med' 'blev beskadiget af'  "; # Vi tilføjer multikandidater til de numeriske sammenligningsoperatorer, fordi vi sammenligner dem numerisk, # men det giver ikke mening at få objekterne til at tvinge til en numerisk type. # (Hvis de tvang, behøvede vi ikke nødvendigvis at tilføje disse operatorer.) # Vi kunne også have defineret helt nye operatorer på samme måde. multi sub infix: " <=> " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . masse <=> $b . mass } multi sub infix: " < " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . masse < $b . mass } multi sub infix: " > " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . masse > $b . masse } multi sub infix: " == " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . masse == $b . masse } # Definer en ny multidispatcher, og tilføj nogle typebegrænsninger til parametrene. # Hvis vi ikke definerede det, ville vi have fået en generisk, der ikke havde begrænsninger. proto sub kolliderer ( Stellar-Object:D $, Stellar-Object:D $ ) {*} # Ingen grund til at gentage typerne her, da de er de samme som prototypen. # 'hvor'-begrænsningen gælder teknisk set kun for $b, ikke hele signaturen. # Bemærk, at 'hvor'-begrænsningen bruger '<'-operatorkandidaten, vi tilføjede tidligere. multi sub collide ( $a , $b hvor $a < $b ) { sig "$a.name() blev @destroyed.pick() af $b.name()" ; } multi sub collide ( $a , $b hvor $a > $b ) { # redispatch til den forrige kandidat med argumenterne byttet sammen med $b , $a ; } # Dette skal være efter de to første, fordi de andre # har 'hvor'-begrænsninger, som bliver tjekket i den # rækkefølge, underordnede blev skrevet. (Denne ville altid matche. ) multi sub collide ( $a , $b ){ # randomiser rækkefølgen my ( $n1 , $n2 ) = ( $a . navn , $b . navn ). vælg (*); sig "$n1 @damaged.pick() $n2" ; } # De følgende to kandidater kan være hvor som helst efter protoen, # fordi de har mere specialiserede typer end de foregående tre. # Hvis skibene har ulige masse, bliver en af ​​de to første kandidater kaldt i stedet. multi sub collide ( Rumskib $a , Rumskib $b hvor $a == $b ){ my ( $n1 , $n2 ) = ( $a . navn , $b . navn ). vælg (*); sige "$n1 kolliderede med $n2, og begge skibe var " , ( @destroyed . pick , 'venstre beskadiget' ). pick ; } # Du kan pakke attributterne ud i variabler i signaturen. # Du kunne endda have en begrænsning på dem `(:masse($a) hvor 10)`. multi sub collide ( Asteroid $ (: masse ( $a )), Asteroid $ (: masse ( $b )) ){ sig "to asteroider kolliderede og kombinerede til en større asteroide med masse { $a + $b }" ; } mit rumskib $Enterprise .= nyt (: masse ( 1 ),: navn ( 'The Enterprise' )); kollidere Asteroide . ny (: masse ( .1 )), $Enterprise ; kollidere $Enterprise , Spaceship . ny (: masse ( .1 )); kollider $Enterprise , Asteroide . ny (: masse ( 1 )); kollidere $Enterprise , Spaceship . ny (: masse ( 1 )); kollidere Asteroide . ny (: masse ( 10 )), Asteroide . ny (: masse ( 5 ));

Python

På sprog, der ikke understøtter flere afsendelser på syntaksniveau, såsom Python , er det generelt muligt at bruge flere afsendelser ved hjælp af udvidelsesbiblioteker. For eksempel implementerer multimethods.py-modulet [6] CLOS-lignende multimetoder i Python uden at ændre syntaks eller sprognøgleord.

fra multimethods import Afsendelse fra game_objects import Asteroide , rumskib fra game_behaviors import ASFunc , SSFunc , SAFunc collide = Dispatch ( ) kolliderer . add_rule (( Asteroide , Rumskib ), ASFunc ) kolliderer . add_rule (( Rumskib , Rumskib ), SSFunc ) kolliderer . add_rule (( Rumskib , Asteroide ), SAFunc ) def AAFunc ( a , b ): """Opførsel når asteroide rammer asteroide""" # ...definer ny adfærd... kollider . add_rule (( Asteroide , Asteroide ), AAFunc ) # ...senere... kollidere ( ting1 , ting2 )

Funktionelt ligner dette meget CLOS-eksemplet, men syntaksen følger standard Python-syntaksen.

Ved at bruge Python 2.4 dekoratorer skrev Guido van Rossum et eksempel på implementering af multimetoder [7] med en forenklet syntaks:

@multimethod ( Asteroid , Asteroid ) def collide ( a , b ): """Adfærd, når asteroide rammer asteroide""" # ...definer ny adfærd... @multimethod ( Asteroid , Spaceship ) def collide ( a , b ) : """Adfærd, når asteroide rammer rumskib""" # ...definer ny adfærd... # ... definer andre multimetoderegler ...

og derefter defineres dekorationsmultimetoden.

PEAK-Rules-pakken implementerer multiple afsendelser med en syntaks svarende til ovenstående eksempel. [otte]

Multiple afsendelsesemulering

Java

På sprog, der kun har en enkelt afsendelse, såsom Java , vil denne kode se sådan ud (dog kan besøgsmønsteret hjælpe med at løse dette problem):

/* Eksempel med sammenligning af køretidstype via Javas "instanceof"-operator */ interface Collideable { /* At gøre dette til en klasse ville ikke ændre demonstrationen. */ void collideWith ( Collideable other ); } klasse Asteroid implementerer Collideable { public void collideWith ( Collideable other ) { if ( other instanceof Asteroid ) { // Håndter Asteroid-Asteroid collision. } else if ( andre forekomst af rumskib ) { // Håndter kollision mellem asteroide og rumskib. } } } klasse Rumskib implementerer Collideable { public void collideWith ( Collideable other ) { if ( other instanceof Asteroid ) { // Håndter rumskib-asteroide kollision. } else if ( andre forekomst af rumskib ) { // Håndter rumskib-rumskib kollision. } } }

C

C har ikke dynamisk forsendelse, så det skal implementeres manuelt i en eller anden form. En opregning bruges ofte til at identificere en undertype af et objekt. Dynamisk afsendelse kan implementeres ved at slå denne værdi op i grentabellen med funktionsmarkører. Her er et simpelt eksempel i C:

typedef void ( * CollisionCase )(); void collision_AA () { /* Asteroide-Asteroid collision handling */ }; void collision_AS () { /* Asteroide-Ship collision handling */ }; void collision_SA () { /* Ship-Asteroid collision handling */ }; void collision_SS () { /* skib-til-skib kollisionshåndtering */ }; typedef enum { asteroide = 0 _ rumskib , num_thing_types /* er ikke en objekttype, bruges til at finde antallet af objekter */ } Ting ; CollisionCase collisionCases [ num_thing_types ][ num_thing_types ] = { { & collision_AA , & collision_AS }, { & collision_SA , & collision_SS } }; void kollidere ( ting a , ting b ) { ( * collisionCases [ a ][ b ])(); } int main () { kollidere ( rumskib , asteroide ); }

C++

Fra 2015 understøtter C++ kun enkelt afsendelse, selvom understøttelse af flere afsendelser er under overvejelse. [9]  Løsningerne for denne begrænsning er ens: enten ved hjælp af besøgsmønsteret eller dynamisk casting:

// Eksempel med sammenligning af køretidstype via dynamic_cast struct Thing { virtual void collideWith ( Thing & other ) = 0 ; }; struct Asteroid : Thing { void kolliderer med ( ting og andet ) { // dynamic_cast til en pointertype returnerer NULL hvis castet mislykkes // (dynamic_cast til en referencetype ville give en undtagelse ved fejl) if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( & other )) { // håndtere Asteroide-Asteroid collision } else if ( Rumskib * rumskib = dynamisk_kast < Rumskib *> ( & andet )) { // håndtere asteroide-rumskib kollision } andet { // standard kollisionshåndtering her } } }; struct Spaceship : Thing { void kolliderer med ( ting og andet ) { if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( & other )) { // håndtere rumskib-asteroide kollision } else if ( Rumskib * rumskib = dynamic_cast < Rumskib *> ( & andet )) { // håndtere rumskib-rumskib kollision } andet { // standard kollisionshåndtering her } } };

eller opslagstabeller for henvisninger til metoder:

#include <typeinfo> #include <uordnet_kort> typedef usigneret uint4 ; typedef usigneret lang lang uint8 ; klasse ting { beskyttet : Ting ( const uint4 cid ) : tid ( cid ) {} const uint4 tid ; // skriv 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 ) { kollisionstilfælde . indsæt ( CollisionHandlerMap :: værditype ( nøgle ( id1 , id2 ), handler )); } statisk uint8 nøgle ( const uint4 id1 , const uint4 id2 ) { returner uint8 ( id1 ) << 32 | id2 ; } statisk CollisionHandlerMap collisionCases ; offentligt : void kolliderer med ( ting og andet ) { CollisionHandlerMap :: const_iterator handler = collisionCases . find ( nøgle ( tid , andet . tid )); if ( handler != kollisionCases . end ()) { ( denne ->* handler -> anden )( anden ); // pointer-to-method call } else { // standard kollisionshåndtering } } }; klasse Asteroide : offentlig ting { void asteroid_collision ( Thing & Other ) { /*handle Asteroid-Asteroid collision*/ } void spaceship_collision ( Thing & Other ) { /*handle Asteroid-Spaceship collision*/ } offentligt : Asteroide () : Ting ( cid ) {} statisk tomrum initCases (); statisk konst uint4 cid ; }; klasse Rumskib : offentlig ting { void asteroid_collision ( Thing & Other ) { /*handle rumskib-asteroide kollision*/ } void spaceship_collision ( ting og andet ) { /*handle rumskib-rumskib kollision*/ } offentligt : Rumskib () : Ting ( cid ) {} statisk tomrum initCases (); statisk konst uint4 cid ; // klasse-id }; Thing :: CollisionHandlerMap Thing :: collisionCases ; const uint4 Asteroide :: cid = typeid ( Asteroide ). hash_kode (); const uint4 Rumskib :: cid = typeid ( Rumskib ). hash_kode (); void Asteroid::initCases () { addHandler ( cid , cid , ( CollisionHandler ) & Asteroid :: asteroid_collision ); addHandler ( cid , Rumskib :: cid , ( CollisionHandler ) & Asteroid :: spaceship_collision ); } void Spaceship::initCases () { addHandler ( cid , Asteroide :: cid , ( CollisionHandler ) & Spaceship :: asteroid_collision ); addHandler ( cid , cid , ( CollisionHandler ) & Spaceship :: spaceship_collision ); } int main () { Asteroide :: initCases (); rumskib :: initCases (); Asteroide a1 , a2 ; Rumskib s1 , s2 ; a1 . kollidere med ( a2 ); a1 . kollidereMed ( s1 ); s1 . kollidereMed ( s2 ); s1 . kollidereMed ( a1 ); }

yomm11-biblioteket [10] giver dig mulighed for at automatisere denne tilgang.

I sin bog The Design and Evolution of C++ nævner Stroustrup, at han godt kan lide konceptet med multimetoder, og at han overvejede at implementere dem i C++, men hævder, at han ikke kunne finde et eksempel på en effektiv (i sammenligning) med virtuelle funktioner til at implementere dem og løse nogle mulige type uklarhedsproblemer. Han argumenterer endvidere for, at selvom det ville være rart at implementere understøttelse af dette koncept, kan det tilnærmes ved dobbelt afsendelse eller en typebaseret opslagstabel som beskrevet i C/C++-eksemplet ovenfor, så denne opgave er af lav prioritet i udviklingen af fremtidige versioner af sproget. . [elleve]

Implementering i programmeringssprog

Understøttelse af multimetoder på andre sprog via udvidelser:

Multiparametertypeklasser i Haskell  og Scala kan også bruges til at emulere multimetoder.

Noter

  1. Muschevici, Radu; Potanin, Alex; Tempero, Ewan; Noble, James (2008). "Multiple forsendelse i praksis" . Proceedings af den 23. ACM SIGPLAN-konference om objektorienterede programmeringssystemers sprog og applikationer . OOPSLA '08 (Nashville, TN, USA: ACM): 563–582
  2. Giuseppe Castagna; Giorgio Ghelli & Giuseppe Longo (1995). "En beregning for overbelastede funktioner med subtyping." Arkiveret 18. november 2018 på Wayback Machine . Information og beregning  (Akademisk presse)  117  (1): 115–135
  3. Castagna, Giuseppe (1996). Objektorienteret programmering: A Unified Foundation . Birkhauser. s. 384.
  4. Giuseppe Castagna (1995). "Kovarians og kontravarians: konflikt uden årsag" Arkiveret 20. november 2018 på Wayback Machine . Transaktioner på programmeringssprog og systemer (TOPLAS)  (ACM)  17  (3). doi : 10.1145/203095.203096
  5. Kim Bruce; Luca Cardelli; Giuseppe Castagna; Gary T. Surdej; Benjamin Pierce (1995). "Om binære metoder" Arkiveret 19. november 2018 på Wayback Machine . Teori og praksis for objektsystemer  1  (3)
  6. multimethods.py Arkiveret 9. marts 2005 på Wayback Machine , Multiple afsendelse i Python med konfigurerbar afsendelsesopløsning af David Mertz, et al.
  7. Fem-minutters multimetoder i Python . Hentet 16. juli 2016. Arkiveret fra originalen 29. maj 2021.
  8. "PEAK-Rules 0.5a1.dev" Arkiveret 14. marts 2017 på Wayback Machine . Python-pakkeindeks . Hentet 21. marts 2014.
  9. Arkiveret kopi . Hentet 16. juli 2016. Arkiveret fra originalen 17. marts 2016.
  10. yomm11 Arkiveret 2. juni 2016 på Wayback Machine , Open Multi-Methods for C++11 af Jean-Louis Leroy.
  11. Stroustrup, Bjarne (1994). Afsnit 13.8. Designet og udviklingen af ​​C++ . Indianapolis, IN, USA: Addison Wesley. ISBN  0-201-54330-3 .
  12. Steele, Guy L. (1990). kapitel 28. Fælles LISP: The Language Archived December 17, 2017 at the Wayback Machine . Bedford, MA, USA: Digital Press. ISBN  1-55558-041-6 .
  13. "Type classes: exploring the design space" Arkiveret 12. august 2016 på Wayback Machine . 1997-05-02.
  14. "Elixir Lang | Kom godt i gang | Moduler" Arkiveret 20. juli 2016 på Wayback Machine . Hentet 2016-02-21.
  15. "Baggrund og mål" Arkiveret 4. april 2020 på Wayback Machine . Hentet 2008-04-13.
  16. "Visitor Pattern Versus Multimethods" Arkiveret 5. februar 2021 på Wayback Machine . Hentet 2008-04-13.
  17. "Cecil Language" Arkiveret 1. september 2016 på Wayback Machine . Hentet 2008-04-13.
  18. "How S4 Methods Work" Arkiveret 10. maj 2021 på Wayback Machine  (PDF). Hentet 2008-04-13.
  19. "Methods" Arkiveret 17. juli 2016 på Wayback Machine . Julia-manualen . Julialang. Hentet 11. maj 2014.
  20. "Multimethods in Groovy" Arkiveret 12. august 2011 på Wayback Machine . Hentet 2008-04-13.
  21. "Methods - LassoGuide 9.2" Arkiveret 13. juni 2021 på Wayback Machine . Hentet 2014-11-11.
  22. "Perl 6 FAQ" Arkiveret 13. marts 2012 på Wayback Machine . Hentet 2008-04-13.
  23. "Multiple Dispatch in Seed7" Arkiveret 29. januar 2021 på Wayback Machine . Hentet 2011-04-23
  24. "Multimethods in Clojure" Arkiveret 20. september 2015 på Wayback Machine . Hentet 2008-09-04.
  25. "Multimethods in C# 4.0 With 'Dynamic'" Arkiveret 25. august 2009 på Wayback Machine . Hentet 2009-08-20.
  26. "The Fortress Language Specification, Version 1.0" Arkiveret 20. januar 2013 på Wayback Machine (PDF). Hentet 2010-04-23.
  27. "TADS 3 System Manual" Arkiveret 14. februar 2017 på Wayback Machine . Hentet 2012-03-19
  28. "Multiple dispatch" Arkiveret 15. juli 2016 på Wayback Machine .
  29. "Nim Manual" Arkiveret 24. september 2017 på Wayback Machine . Hentet 2015-05-08.