Multimethod ( englanniksi multimethod ) tai multiple dispatch ( englanniksi multiple dispatch ) on ohjelmointikielien mekanismi, jonka avulla voit valita yhden useista toiminnoista riippuen dynaamisista tyypeistä tai argumenttiarvoista (esimerkiksi menetelmän ylikuormitus joissakin ohjelmointikielissä) . Se on yksittäisen lähetyksen ( virtuaalifunktiot ) laajennus, jossa menetelmän valinta tehdään dynaamisesti sen kohteen todellisen tyypin perusteella, johon menetelmää kutsuttiin. Multiple dispatch yleistää dynaamisen lähettämisen tapauksissa, joissa on kaksi tai useampia objekteja.
"Common Lisp Object System" ( CLOS ) tukee monimenetelmiä .
Ohjelmakehittäjillä on tapana ryhmitellä lähdekoodi nimettyihin lohkoihin, joita kutsutaan kutsuiksi, proseduureiksi, aliohjelmiksi , funktioiksi tai menetelmiksi. Toiminnon koodi suoritetaan kutsumalla sitä, mikä koostuu sen nimen osoittaman koodinpalan suorittamisesta. Tässä tapauksessa ohjaus siirretään tilapäisesti kutsutulle funktiolle; kun tämä toiminto on valmis, ohjaus siirtyy yleensä takaisin funktiokutsua seuraavalle käskylle.
Toimintojen nimet valitaan yleensä kuvaamaan niiden tarkoitusta. Joskus on tarpeen nimetä useita toimintoja samalla nimellä, yleensä koska ne suorittavat käsitteellisesti samanlaisia tehtäviä, mutta toimivat erityyppisten syöttötietojen kanssa. Tällaisissa tapauksissa funktion nimi sen kutsupaikassa ei riitä määrittämään kutsuttavan koodilohkon. Nimen lisäksi tässä tapauksessa käytetään myös kutsutun funktion argumenttien määrää ja tyyppiä funktion tietyn toteutuksen valitsemiseen.
Perinteisemmissä yhden lähetyksen olioohjelmointikielissä, kun menetelmää kutsutaan (viestin lähettäminen Smalltalkissa , jäsenfunktion kutsuminen C++ :ssa ), yhtä sen argumenteista käsitellään erityisellä tavalla ja sen avulla määritetään, mikä mahdollisesti monia) menetelmiä tällä nimellä on kutsuttava. Monissa kielissä tämä erityinen argumentti ilmaistaan syntaktisesti, esimerkiksi useissa ohjelmointikielissä erityinen argumentti asetetaan ennen pistettä, kun menetelmää kutsutaan:
erikoismenetelmä (muut, argumentit, tässä)joten lion.sound() tuottaa karjun ja sparrow.sound() tuottaa sirkutuksen.
Sitä vastoin kielissä, joissa on useita lähetyksiä, valittu menetelmä on yksinkertaisesti menetelmä, jonka argumentit vastaavat funktiokutsussa olevien argumenttien määrää ja tyyppiä. Tässä ei ole mitään erityistä argumenttia, joka "omistaa" tietyn kutsun viittaaman funktion tai menetelmän.
Common Lisp Object System (CLOS) on yksi ensimmäisistä ja tunnetuista useiden lähetysten toteutuksista.
Kun työskentelet sellaisten kielten kanssa, joiden tietotyypit erotetaan käännöshetkellä, valinta käytettävissä olevista toimintovaihtoehdoista voi tapahtua käännöshetkellä. Tällaisten vaihtoehtoisten funktiovaihtoehtojen luomista käännöshetkellä kutsutaan yleisesti funktion ylikuormitukseksi .
Ohjelmointikielissä, jotka määrittävät tietotyypit ajon aikana (myöhäinen sidonta), toimintovaihtoehtojen valinnan on tapahduttava ajon aikana dynaamisesti määritettyjen funktion argumenttityyppien perusteella. Funktioita, joiden vaihtoehtoiset toteutukset valitaan tällä tavalla, kutsutaan yleisesti multimetodeiksi.
Toimintokutsujen dynaamiseen lähettämiseen liittyy joitain ajonaikaisia kustannuksia. Joissakin kielissä ero funktion ylikuormituksen ja monimenetelmien välillä voi hämärtyä, kun kääntäjä määrittää, voidaanko kutsutun funktion valinta tehdä käännösvaiheessa vai tarvitaanko hitaampaa lähetystä ajon aikana.
Arvioidakseen kuinka usein monivälitystä käytännössä käytetään, Muschevici ym . [1] tutkivat sovelluksia, jotka käyttävät dynaamista jakelua. He analysoivat yhdeksää sovellusta, enimmäkseen kääntäjiä, jotka oli kirjoitettu kuudella eri ohjelmointikielellä: Common Lisp Object System , Dylan , Cecil, MultiJava, Diesel ja Nice. Tulokset osoittavat, että 13–32 % yleisistä funktioista käyttää yhden argumentin dynaamista kirjoitusta, kun taas 2,7–6,5 % funktioista käyttää usean argumentin dynaamista kirjoitusta. Loput 65–93 % yleisistä funktioista käyttävät vain tiettyä menetelmää (ylikuormitettu), joten niiden ei katsottu käyttävän argumenttien dynaamista tyyppiä. Lisäksi tutkimus raportoi, että 2–20 prosentilla yleisistä toiminnoista oli kaksi ja 3–6 prosentilla kolme erityistä toteutusta. Konkreettisia toteutuksia sisältävien toimintojen osuus oli nopeasti laskussa.
Monen puhelun kielten teorian kehittivät ensimmäisenä Castagna ym. määrittelemällä mallin ylikuormitetuille funktioille myöhäisellä sidoksella [2] [3] . Tämä antoi ensimmäisen formalisoinnin olio-ohjelmointikielten kovarianssin ja vastavariaatioiden [4] ongelmalle ja ratkaisun binäärimenetelmien [5] ongelmaan .
Jotta voitaisiin paremmin ymmärtää ero monimenetelmien ja kertalähetyksen välillä, voidaan havainnollistaa seuraava esimerkki. Kuvittele peli, jossa monien muiden esineiden ohella on asteroideja ja avaruusaluksia. Kun kaksi kohdetta törmäävät, ohjelman on valittava tietty toimintoalgoritmi sen mukaan, mikä törmäsi mihinkin.
Monimenetelmäkielessä, kuten Common Lisp , koodi näyttäisi tältä:
( defgeneric törmäys ( x y )) ( defmethod törmää (( x asteroidi ) ( y asteroidi )) ;; asteroidi törmää asteroidiin ) ( defmethod törmää (( x asteroidi ) ( y avaruusalus )) ;;asteroidi törmää avaruusalukseen ) ( defmethod törmää (( x avaruusalus ) ( y asteroidi )) ;;avaruusalus törmää asteroidiin ) ( defmethod törmää (( x avaruusalus ) ( y avaruusalus )) ;;avaruusalus törmää avaruusalukseen )ja samoin muille menetelmille. Eksplisiittistä tarkistusta ja "dynaamista suoraa" ei käytetä tässä.
Useiden lähetysten yhteydessä perinteinen lähestymistapa luokkien menetelmien määrittämiseen ja niiden tallentamiseen objekteihin tulee vähemmän houkuttelevaksi, koska jokainen törmäysmenetelmä viittaa kahteen eri luokkaan yhden sijasta. Näin ollen menetelmän kutsumisen erityinen syntaksi yleensä katoaa, joten menetelmäkutsu näyttää täsmälleen normaalilta funktiokutsulta ja menetelmät eivät ryhmitetä luokkien mukaan, vaan yleisiin funktioihin .
Raku, kuten aiemmat versiot, käyttää todistettuja ideoita muista kielistä ja tyyppijärjestelmistä tarjotakseen vakuuttavia etuja kääntäjäpuolen koodianalyysissä ja tehokkaassa semantiikkassa useiden lähetysten avulla.
Siinä on sekä multimetodeja että monialirutiineja. Koska useimmat lausunnot ovat alirutiineja, on myös lauseita, joissa on useita lähetyksiä.
Tavallisten tyyppirajoitusten lisäksi siinä on myös "jos"-tyyppisiä rajoituksia, joiden avulla voit luoda erittäin erikoistuneita aliohjelmia.
osajoukko Realin massa missä 0 ^ ..^ Inf ; rooli Stellar-Object { has Mass $ .massa vaaditaan ; menetelmän nimi () palauttaa Str {...}; } luokan asteroidi tekee Stellar-Objectin { menetelmän nimi () { 'asteroidi' } } class Avaruusalus tekee Stellar-Objectin { has Str $.name = 'jokin nimetön avaruusalus' ; } my Str @detroyed = < hävitetty tuhottu mangled >; my Str @damaged = " vaurioitunut 'törmäsi' 'vaurioitui' "; # Lisäämme useita ehdokkaita numeerisiin vertailuoperaattoreihin, koska vertaamme niitä numeerisesti, # mutta ei ole järkevää pakottaa objektit numeeriseen tyyppiin. # (Jos he pakottaisivat, meidän ei välttämättä tarvitsisi lisätä näitä operaattoreita. ) # Olisimme voineet määritellä myös täysin uusia operaattoreita samalla tavalla. multi subinfix : " <=> " ( Tähti-objekti:D $a , tähti-objekti:D $b ) { $a . massa <=> $b . massa } moni aliliite : " < " ( Tähti-objekti:D $a , Tähti-objekti:D $b ) { $a . massa < $b . massa } moni aliliite : " > " ( Tähti-objekti:D $a , tähti-objekti:D $b ) { $a . massa > $b . massa } moni aliliite : " == " ( Tähti-objekti:D $a , tähti-objekti:D $b ) { $a . massa == $b . massa } # Määrittele uusi monivälittäjä ja lisää parametreihin joitain tyyppirajoituksia. # Jos emme olisi määrittäneet sitä, olisimme saaneet yleisen, jolla ei ole rajoituksia. proto - alitörmäys ( tähti -objekti:D $, tähti-objekti:D $ ) {*} # Tyyppejä ei tarvitse toistaa tässä, koska ne ovat samat kuin prototyyppi. # 'where' -rajoitus koskee teknisesti vain $b:tä, ei koko allekirjoitusta. # Huomaa, että "where" -rajoite käyttää aiemmin lisäämäämme "<"-operaattoriehdokasta. monen osatörmäys ( $a , $b jossa $a < $b ) { sano "$b.name() @tuhoittui.nimi() " ; } monen osatörmäys ( $a , $ b jossa $a > $b ) { # uudelleenlähetys edelliselle ehdokkaalle argumenteilla, jotka on vaihdettu samaan kanssa $b , $a ; } # Tämän täytyy olla kahden ensimmäisen jälkeen, koska muilla # on "minne"-rajoitukset, jotka tarkistetaan siinä # järjestyksessä, jossa subs kirjoitettiin. (Tämä sopisi aina yhteen. ) Multi sub törmää ( $a , $b ){ # satunnaista järjestys my ( $n1 , $n2 ) = ( $a . nimi , $b . nimi ). valita (*); sano "$n1 @damaged.pick() $n2" ; } # Seuraavat kaksi ehdokasta voivat olla missä tahansa proton jälkeen, # koska niillä on erikoistuneempia tyyppejä kuin kolme edellistä. # Jos alusten massa on epätasainen, toinen kahdesta ensimmäisestä ehdokkaasta kutsutaan tilalle. usean osatörmäyksen ( Avaruusalus $a , Avaruusalus $ b jossa $ a == $b ){ minun ( $n1 , $n2 ) = ( $a . nimi , $b . nimi ). valita (*); sano "$n1 törmäsi $n2:een ja molemmat alukset olivat " , ( @detroyed . pick , 'vasen vahingoittunut' ). valita ; } # Voit purkaa määritteet muuttujiksi allekirjoituksen sisällä. # Voit jopa asettaa niille rajoituksen `(:mass($a) missä 10)`. moniosatörmäys ( Asteroidi $ (: massa ( $a )), Asteroidi $ (: massa ( $b ))){ sano "kaksi asteroidia törmäsivät ja yhdistyivät yhdeksi suuremmaksi asteroidiksi, jonka massa on { $a + $b }" ; } minun avaruusalus $Enterprise .= uusi (: massa ( 1 ),: nimi ( 'The Enterprise' )); törmää asteroidiin . uusi (: massa ( .1 )), $Enterprise ; törmää $Enterprise , avaruusalus . uusi (: massa ( .1 )); törmäävät $Enterprise , Asteroidi . uusi (: massa ( 1 )); törmää $Enterprise , avaruusalus . uusi (: massa ( 1 )); törmää asteroidiin . uusi (: massa ( 10 )), Asteroidi . uusi (: massa ( 5 ));Kielessä, joka ei tue useaa lähetystä syntaksitasolla, kuten Python , on yleensä mahdollista käyttää useaa lähetystä laajennuskirjastojen avulla. Esimerkiksi multimethods.py-moduuli [6] toteuttaa CLOS-tyylisiä monimenetelmiä Pythonissa muuttamatta syntaksia tai kielen avainsanoja.
multimethods -tuonti Lähettäminen game_objects - tuonnista Asteroidi , Avaruusalus from game_behaviors tuonti ASFunc , SSFunc , SAFunc törmää = Lähetys ( ) törmää . add_rule (( Asteroidi , Avaruusalus ), ASFunc ) törmää . add_rule (( avaruusalus , avaruusalus ), SSFunc ) törmää . add_rule (( Avaruusalus , Asteroidi ), SAFunc ) def AAFunc ( a , b ): """Käyttäytyminen asteroidin osuessa asteroidiin""" # ...määrittää uusi käyttäytyminen... törmää . add_rule (( Asteroidi , Asteroidi ), AAFunc ) # ...myöhemmin... törmää ( asia1 , asia2 )Toiminnallisesti tämä on hyvin samanlainen kuin CLOS-esimerkki, mutta syntaksi noudattaa standardi Python-syntaksia.
Käyttämällä Python 2.4 -dekoraattoreita Guido van Rossum kirjoitti esimerkin monimetodeista [7] yksinkertaistetulla syntaksilla:
@multimethod ( Asteroid , Asteroid ) def törmäys ( a , b ): """Käyttäytyminen asteroidin osuessa asteroidiin""" # ...määritä uusi käyttäytyminen... @multimethod ( Asteroid , Asteroid ) def törmäys ( a , b ) : """Käyttäytyminen asteroidin osuessa avaruusalukseen""" # ...määritä uusi käyttäytyminen... # ... määritä muut monimenetelmäsäännöt ...ja sitten määritellään sisustajan monimenetelmä.
PEAK-Rules-paketti toteuttaa usean lähettämisen syntaksilla, joka on samanlainen kuin yllä oleva esimerkki. [kahdeksan]
Kieleillä, joilla on vain yksi lähetys, kuten Java , tämä koodi näyttää tältä ( vierailijamalli voi kuitenkin auttaa ratkaisemaan tämän ongelman):
/* Esimerkki suoritusaikatyyppivertailusta Javan "instanceof"-operaattorilla */ käyttöliittymä Collideable { /* Tämän luokan tekeminen ei muuttaisi esittelyä. */ void collideWith ( Collideable other ); } luokan Asteroidi toteuttaa Collideable { public void törmätäWith ( Collideable other ) { if ( other instanceof Asteroid ) { // Käsittele asteroidin ja asteroidin törmäystä. } else if ( avaruusaluksen muu esiintymä ) { // Käsittele asteroidi-avaruusaluksen törmäystä. } } } luokan avaruusalus toteuttaa Collideable { public void törmätäWith ( Collideable other ) { if ( other instanceof Asteroid ) { // Käsittele avaruusaluksen ja asteroidin törmäystä. } else if ( other instanceof Spaceship ) { // Käsittele avaruusalus-avaruusaluksen törmäystä. } } }C:llä ei ole dynaamista lähetystä, joten se täytyy toteuttaa käsin muodossa tai toisessa. Luetteloa käytetään usein tunnistamaan objektin alatyyppi. Dynaaminen lähetys voidaan toteuttaa etsimällä tämä arvo toimintoosoittimien haarataulukosta. Tässä on yksinkertainen esimerkki C:ssä:
typedef void ( * CollisionCase )(); void collision_AA () { /* Asteroidin ja asteroidin törmäyksen käsittely */ }; void collision_AS () { /* Asteroidi-laivan törmäyksen käsittely */ }; void collision_SA () { /* Aluksen ja asteroidin törmäyksen käsittely */ }; void collision_SS () { /* alusten välisten törmäysten käsittely */ }; typedef enum { asteroidi = 0_ _ avaruusalus , num_thing_types /* ei ole objektityyppi, sitä käytetään objektien lukumäärän etsimiseen */ } Asia ; CollisionCase collisionCases [ num_thing_types ][ num_thing_types ] = { { & collision_AA , & collision_AS }, { & collision_SA , & collision_SS } }; void törmää ( asia a , asia b ) { ( * törmäystapaukset [ a ][ b ])(); } int main () { törmätä ( avaruusalus , asteroidi ); }Vuodesta 2015 lähtien C++ tukee vain yksittäistä lähetystä, vaikka useiden lähetysten tukea harkitaan. [9] Tämän rajoituksen kiertotavat ovat samanlaiset: joko vierailijamallin tai dynaamisen suoratoiston avulla:
// Esimerkki suoritusajan tyyppivertailusta dynamic_castin avulla struct Thing { virtuaalinen void törmätä ( asia ja muu ) = 0 ; }; rakenne Asteroidi : Asia { void collideWith ( asia ja muut ) { // dynaaminen_cast osoitintyyppiin palauttaa NULL-arvon, jos heitto epäonnistuu // (dynaaminen_lähetys viitetyyppiin aiheuttaisi poikkeuksen epäonnistumisen yhteydessä) if ( Asteroid * asteroid = dynaaminen_cast < Asteroid *> ( & other )) { // käsittele asteroidin ja asteroidin törmäystä } else if ( Avaruusalus * avaruusalus = dynaaminen_cast < Avaruusalus *> ( & muu )) { // käsittele asteroidin ja avaruusaluksen törmäystä } else { // oletusarvoinen törmäyskäsittely täällä } } }; struct Avaruusalus : Thing { void collideWith ( asia ja muut ) { if ( Asteroidi * asteroidi = dynaaminen_cast < Asteroid *> ( & muu )) { // käsittele avaruusaluksen ja asteroidin törmäystä } else if ( Avaruusalus * avaruusalus = dynaaminen_cast < Avaruusalus *> ( ja muut )) { // käsittele avaruusaluksen ja avaruusaluksen törmäystä } else { // oletusarvoinen törmäyskäsittely täällä } } };tai hakutaulukot menetelmiin viittaaville:
#include <tyyppitiedot> #include <järjestämätön_kartta> typedef allekirjoittamaton uint4 ; typedef unsigned pitkä pitkä uint8 ; luokka asia { suojattu : Asia ( const uint4 cid ) : tid ( cid ) {} const uint4 tid ; // kirjoita id typedef void ( Thing ::* CollisionHandler )( Thing & muu ); typedef std :: unordered_map < uint8 , CollisionHandler > CollisionHandlerMap ; static void addHandler ( const uint4 id1 , const uint4 id2 , const CollisionHandler käsittelijä ) { törmäystapaukset . insert ( CollisionHandlerMap :: arvo_tyyppi ( avain ( id1 , id2 ), käsittelijä )); } staattinen uint8 avain ( const uint4 id1 , const uint4 id2 ) { palauttaa uint8 ( id1 ) << 32 | id2 ; } staattinen CollisionHandlerMap collisionCases ; julkinen : void collideWith ( asia ja muut ) { CollisionHandlerMap :: const_iterator handler = törmäystapaukset . löytää ( avain ( tid , muu . tid )); if ( käsittelijä != törmäysCases . end ()) { ( tämä ->* käsittelijä -> toinen )( muu ); // pointer-to-method call } else { // oletus törmäyskäsittely } } }; luokan asteroidi : julkinen asia { void asteroid_collision ( Thing & other ) { /*handle Asteroid-Asteroid Collision*/ } void spaceship_collision ( Thing & other ) { /*käsitellä asteroidi-avaruusaluksen törmäystä*/ } julkinen : Asteroidi () : Asia ( cid ) {} static void initCases (); staattinen const uint4 cid ; }; luokan avaruusalus : julkinen asia { void asteroid_collision ( Thing & other ) { /*käsitellä avaruusaluksen ja asteroidin törmäystä*/ } void spaceship_collision ( Thing & other ) { /*handle Spaceship-Spaceship collision*/ } julkinen : Avaruusalus () : Asia ( cid ) {} static void initCases (); staattinen const uint4 cid ; // luokan tunnus }; Asia :: CollisionHandlerMap Thing :: collisionCases ; const uint4 Asteroidi :: cid = typeid ( Asteroid ). hash_code (); const uint4 Avaruusalus :: cid = typeid ( Spaceship ). 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 ) & avaruusalus :: asteroid_collision ); addHandler ( cid , cid , ( CollisionHandler ) & Spaceship :: spaceship_collision ); } int main () { Asteroidi :: initCases (); avaruusalus :: initCases (); asteroidi a1 , a2 ; avaruusalus s1 , s2 ; a1 . törmää ( a2 ); a1 . törmätä kanssa ( s1 ); s1 . törmätä ( s2 ); s1 . törmää ( a1 ); }Yomm11-kirjasto [10] mahdollistaa tämän lähestymistavan automatisoinnin.
Kirjassaan The Design and Evolution of C++ Stroustrup mainitsee, että hän pitää monimenetelmien käsitteestä ja että hän harkitsi niiden toteuttamista C++:ssa, mutta väittää, ettei hän löytänyt esimerkkiä tehokkaasta (verrattuna) virtuaalisiin toimintoihin. ja ratkaise joitakin mahdollisia tyyppien epäselvyysongelmia. Hän väittää lisäksi, että vaikka olisi mukavaa ottaa käyttöön tuki tälle konseptille, se voidaan approksimoida kaksoislähetyksellä tai tyyppipohjaisella hakutaulukolla, kuten yllä olevassa C/C++-esimerkissä on kuvattu, joten tämä tehtävä on kehitystyössä alhainen prioriteetti. kielen tulevista versioista. [yksitoista]
Tuki monimuotoisille menetelmille muilla kielillä laajennusten kautta:
Haskellin ja Scalan moniparametrityyppisiä luokkia voidaan käyttää myös monimenetelmien emulointiin.