Szerkesztő:DRKMATE/Többszörös feladás

Programozási nyelvek támogatásaSablon:Polymorphism

A többszörös feladás vagy multimetódusok egyes programozási nyelvek olyan jellemzője, amelyben egy függvény vagy metódus dinamikusan feladásra kerülhet a futásidő (dinamikus) típusa vagy általánosabb esetben több argumentumának valamilyen más attribútuma alapján.[1] Ez az egyszeri feladású polimorfizmus általánosítása, ahol egy függvény vagy metódus hívása dinamikusan feladásra kerül annak az objektumnak a származtatott típusa alapján, amelyen a metódust hívták. A többszörös feladás a dinamikus feladást a végrehajtó függvényhez vagy metódushoz irányítja egy vagy több argumentum kombinált jellemzőinek felhasználásával.

A feladás megértése

szerkesztés

A számítógépes szoftverek fejlesztői a forráskódot általában megnevezett blokkokba szervezik, amelyeket különbözőképpen neveznek szubrutinoknak, eljárásoknak, alprogramoknak, függvényeknek vagy módszereknek. A függvényben lévő kódot a függvény meghívásával - a nevére hivatkozó kódrészlet lefuttatásával - hajtják végre. Ez átmenetileg átadja a vezérlést a hívott függvénynek; amikor a függvény végrehajtása befejeződött, a vezérlés jellemzően visszakerül a hívó utasításhoz, amely a hivatkozást követi.

A függvényneveket általában úgy választják ki, hogy azok leírják a függvény feladatát. Néha kívánatos, hogy több függvénynek ugyanazt a nevet adjuk, gyakran azért, mert gyakorlatilag hasonló feladatokat látnak el, de különböző típusú bemeneti adatokkal dolgoznak. Ilyen esetekben a függvényhívás helyén lévő névreferencia nem elegendő a végrehajtandó kódblokk azonosításához. Ehelyett a függvényhívás argumentumainak száma és típusa is felhasználásra kerül a több függvény megvalósítása közötti kiválasztáshoz.

A hagyományosabb, azaz az egyszeres feladású objektumorientált programozási nyelvekben egy metódus meghívásakor (Smalltalkban egy üzenet küldése, C++-ban egy tagfüggvény hívása) az egyik argumentumot speciálisan kezelik, és arra használják, hogy meghatározzák, hogy az adott nevű metódusok (potenciálisan sok) osztálya közül melyiket kell alkalmazni. Sok nyelvben a speciális argumentumot szintaktikailag jelzik; például számos programozási nyelv a speciális argumentumot egy pont elé teszi a metódushívás során: special.method(other, arguments, here), így a lion.sound() üvöltést, míg a sparrow.sound() csiripelést eredményezne.

Ezzel szemben a többszörös diszpécserrel rendelkező nyelvekben a kiválasztott módszer egyszerűen az, amelynek argumentumai megegyeznek a függvényhívás számával és típusával. Nincs olyan speciális argumentum, amely egy adott hívásban végrehajtott függvény/metódus tulajdonosa lenne.

A Common Lisp Object System (CLOS) a többszörös feladás korai és jól ismert példája.

Adattípusok

szerkesztés

Amikor olyan nyelvekkel dolgozunk, amelyek képesek az adattípusok megkülönböztetésére fordítási időben, akkor az alternatívák közötti választás már ekkor megtörténhet. Az ilyen alternatív függvények létrehozását a fordítási idejű kiválasztáshoz általában egy függvény túlterhelésnek nevezik.

Azokban a programozási nyelvekben, amelyek az adattípusok azonosítását a futási időre halasztják (azaz késői kötés), az alternatív függvények közötti választásnak ekkor kell megtörténnie, a függvény argumentumainak dinamikusan meghatározott típusai alapján. Azokat a függvényeket, amelyek alternatív implementációi ilyen módon kerülnek kiválasztásra, legáltalánosabban multimetódusoknak nevezzük.

A függvényhívások dinamikus feladásnak van némi futásidő költsége. Egyes nyelvekben a túlterhelés és a multimetódusok függvényhívás közötti különbségtétel elmosódhat, és a fordító határozza meg, hogy a fordítási idejű kiválasztás alkalmazható-e egy adott függvényhívásra, vagy pedig lassabb futási idejű feladásra van szükség.

Gyakorlati alkalmazás

szerkesztés

Annak becslésére, hogy a gyakorlatban milyen gyakran használják a többszörös feladást, Muschevici et al.[2] olyan programokat vizsgált, amelyek dinamikus feladást használnak. Kilenc, többnyire fordítóprogramot elemeztek, amelyeket hat különböző nyelven írtak: Ezek a nyelvek a Common Lisp Object System, a Dylan, a Cecil, a MultiJava, a Diesel és a Nice. Eredményeik azt mutatják, hogy a generikus függvények 13-32%-a használja egy argumentum dinamikus típusát, míg 2,7-6,5%-uk több argumentum dinamikus típusát. A generikus függvények fennmaradó 65-93%-ának egy konkrét metódusa van (overrider), és így úgy tekintik, hogy nem használják argumentumaik dinamikus típusát. A tanulmány továbbá arról számol be, hogy a generikus függvények 2-20%-ának két, 3-6%-ának pedig három konkrét függvény implementációja van. A számok gyorsan csökkennek a több konkrét overriderrel rendelkező függvények esetében.

A többszörös feladást sokkal erőteljesebben használják a Juliában, ahol a többszörös feladást a nyelv eredetétől fogva központi tervezési koncepciónak tekintették: a Muschevici által elemzett, az általános függvényenkénti átlagos metódusszámra vonatkozó statisztikákat összegyűjtve kiderült, hogy a Julia szabványkönyvtárában több mint kétszer annyi túlterhelést használnak, mint a Muschevici által elemzett többi nyelvben, és több mint tízszer annyit a bináris operátorok [3] esetében.

Az ezekből a tanulmányokból származó adatokat a következő táblázatban foglaljuk össze, ahol a DR feladási arány (dispatch ratio) az egy általános függvényre jutó módszerek átlagos száma; a CR választási arány (choice ratio) a módszerek számának négyzetének átlaga (a nagyszámú módszerrel rendelkező függvények gyakoriságának jobb mérése érdekében);[2][3] a DoS specializációs fok pedig a típus-specifikált argumentumok átlagos száma metódusonként (azaz a feladott argumentumok száma):

Nyelv Átlag # metódusok (DR) Választási arány

(CR)

Feladott argumentumok száma (DoS)
Cecil[2] 2.33 63.30 1.06
Common Lisp (CMU)[2] 2.03 6.34 1.17
Common Lisp (McCLIM)[2] 2.32 15.43 1.17
Common Lisp (Steel Bank)[2] 2.37 26.57 1.11
Diesel[2] 2.07 31.65 0.71
Dylan (Gwydion)[2] 1.74 18.27 2.14
Dylan (OpenDylan)[2] 2.51 43.84 1.23
Julia[3] 5.86 51.44 1.54
Julia (operators only) 28.13 78.06 2.01
MultiJava[2] 1.50 8.92 1.02
Nice[2] 1.36 3.46 0.33

A többszörös feladású nyelvek elméletét először Castagna et al. dolgozta ki, a késői kötéssel[4][5] rendelkező, túlterhelt függvények modelljének meghatározásával. Ez eredményezte az objektumorientált nyelvek[6] kovariancia és kontravariancia problémájának első formalizálását és a bináris módszerek problémájának megoldását.[7]

A többszörös és az egyszeri feladás megkülönböztetését egy példával lehet világosabbá tenni. Képzeljünk el egy játékot, amelynek (felhasználó által látható) tárgyai, űrhajói és aszteroidái vannak. Két objektum ütközésekor előfordulhat, hogy a programnak különböző dolgokat kell tennie attól függően, hogy mi éppen eltalálta.

Beépített többszörös feladással rendelkező nyelvek

szerkesztés

A C# a 4. verzióban[8] (2010. április) vezette be a dinamikus többmódusú metódusok támogatását a 'dynamic' kulcsszóval. A következő példa a 8-as verzióban[9] (2019 szeptember) bevezetett switch-kifejezésekkel együtt mutatja be a multimódszereket. Sok más statikusan gépelt nyelvhez hasonlóan a C# is támogatja a statikus metódus-túlterhelést. [10]A Microsoft arra számít, hogy a fejlesztők a legtöbb esetben a statikus gépelést fogják választani a dinamikus gépeléssel szemben.[11] A 'dynamic' kulcsszó támogatja a COM-objektumokkal és a dinamikusan gépelt .NET-nyelvekkel való átjárhatóságot.

class Program
    {
        static void Main()
        {
            Console.WriteLine(Utkozteto.osszeutkozes(new Aszteroida(101), new Urhajo(300)));
            Console.WriteLine(Utkozteto.osszeutkozes(new Aszteroida(10), new Urhajo(10)));
            Console.WriteLine(Utkozteto.osszeutkozes(new Urhajo(101), new Urhajo(10)));
        }
    }

    static class Utkozteto
    {
        public static string osszeutkozes(UrTargy x, UrTargy y) =>
            ((x.Meret > 100) && (y.Meret > 100)) ?
                "Nagy bumm!" : CollideWith(x as dynamic, y as dynamic);
        private static string CollideWith(Aszteroida x, Aszteroida y) => "a/a";
        private static string CollideWith(Aszteroida x, Urhajo y) => "a/s";
        private static string CollideWith(Urhajo x, Aszteroida y) => "s/a";
        private static string CollideWith(Urhajo x, Urhajo y) => "s/s";
    }

    abstract class UrTargy
    {
        public UrTargy(int size) => Meret = size;

        public int Meret { get; }
    }

    class Aszteroida : UrTargy
    {
        public Aszteroida(int meret) : base(meret) { }
    }

    class Urhajo : UrTargy
    {
        public Urhajo(int meret) : base(meret) { }
    }

Kimenet:

Nagybumm
a/s
s/s

A Groovy egy általános célú, Java kompatibilis/kölcsönösen használható JVM nyelv, amely a Javával ellentétben késői kötést / többszörös diszpatchet használ. [12]

 /*
    A fenti C# példa Groovy implementációja
    A késői kötés ugyanúgy működik, ha nem statikus metódusokat használunk, vagy ha statikusan fordítjuk az osztályokat/módszereket.
    (@CompileStatic annotáció)
    */
    class Program
    {
        static void main(String[] args)
        {
            println Utkozteto.osszeutkozes(new Aszteroida(101), new Urhajo(300))
          println Utkozteto.osszeutkozes(new Aszteroida(10), new Urhajo(10))
          println Utkozteto.osszeutkozes(new Urhajo(101), new Urhajo(10))
        }
    }

    class Collider
    {
        static String osszeutkozes(UrTargy x, UrTargy y)
        {
            (x.meret > 100 && y.meret > 100) ? "Nagy bumm" : collideWith(x, y) // Dinamikus küldés a collideWith módszerhez
  }

        private static String collideWith(Aszteroida x, Aszteroida y) { "a/a" }
        private static String collideWith(Aszteroida x, Urhajo y) { "a/s" }
        private static String collideWith(Urhajo x, Aszteroida y) { "s/a" }
        private static String collideWith(Urhajo x, Urhajo y) { "s/s"}
    }

    class UrTargy
    {
        int meret
      UrTargy(int size) { this.meret = size }
    }

    @InheritConstructors class Aszteroida extends UrTargy { }
    @InheritConstructors class Urhajo extends UrTargy { }

Common lisp

szerkesztés

Egy többszörös feladással rendelkező nyelvben, mint például a Common Lisp, ez inkább így nézhet ki (Common Lisp példa látható):

 (defmethod collide-with((x aszteroida) (y aszteroida))
  ;; az aszteroidával való ütközéssel foglalkozik
  )
(defmethod collide-with((x aszteroida) (y urhajo))
  ;; az űrhajóba csapódó aszteroidával foglalkozik
  )
(defmethod collide-with((x urhajo) (y aszteroida))
  ;; aszteroidába csapódó űrhajóval foglalkozik.
  )
(defmethod collide-with((x urhajo) (y urhajo))
  ;; űrhajóval ütköző űrhajóval foglalkozik.
  )

és hasonlóan a többi módszer esetében is. Explicit tesztelés és "dinamikus kiosztás" nem használatos.

A többszörös feladás mellett a hagyományos elképzelés, miszerint a metódusok osztályokban definiáltak és objektumokban vannak, kevésbé tetszetős - minden egyes fenti, ütközéses metódus két különböző osztályhoz tartozik, nem pedig egyhez. Ezért a metódushívás speciális szintaxisa általában eltűnik, így a metódushívás pontosan úgy néz ki, mint a szokásos függvényhívás, és a metódusok nem osztályokba, hanem általános függvényekbe vannak csoportosítva.

A Julia beépített többszörös feladással rendelkezik, és ez a nyelv tervezésének központi eleme.[3] A fenti példa Julia változataként a következőképpen nézhet ki:

(collide_with(x::Aszteroida, y::Aszteroida) = ... # az aszteroidával való ütközéssel foglalkozik.
collide_with(x::Aszteroida, y::Urhajo) = ... # az űrhajóba csapódó aszteroidával foglalkozik.
collide_with(x::Urhajo, y::Aszteroida) = ... # aszteroidába csapódó űrhajóval foglalkozik.
collide_with(x::Urhajo, y::Urhajo) = ... # űrhajóval ütköző űrhajóval foglalkozik

Next Generation Shell

szerkesztés

A Next Generation Shell beépített többszörös feladással és predikátumfeladással rendelkezik, és ezek központi szerepet játszanak a nyelv tervezésében. [13]

Az azonos nevű metódusok többszörös feladású metódust alkotnak, ezért nincs szükség külön deklarációra.

Amikor egy többszörös átadás metódus meghívására kerül sor, a jelölt módszereket alulról felfelé haladva keresi a rendszer. Amennyiben az argumentumok típusai megegyeznek a paraméterekhez megadott típusokkal, a metódus meghívásra kerül. Ez ellentétben áll sok más nyelvvel, ahol a legtipikusabb típusú egyezés nyer. Egy meghívott metóduson belül egy sikertelen őrfeltétel (ahol a őrfeltétel hamisnak értékelődik) a meghívandó metódus keresését folytatja.

{
	type UrTargy
    type Aszteroida(UrTargy)
    type Spaceship(UrTargy)
}

    F init(o:UrTargy, meret:Int) o.meret = meret

    type Aszteroida(UrTargy)
    F osszeutkozes(x:Aszteroida, y:Aszteroida) "a/a"
F osszeutkozes(x:Aszteroida, y:Urhajo) "a/s"
F osszeutkozes(x:Urhajo, y:Aszteroida) "s/a"
F osszeutkozes(x:Urhajo, y:Urhajo) "s/s"

F osszeutkozes(x:UrTargy, y:UrTargy){
    guard x.meret > 100
    guard y.meret > 100
    "Nagy bumm"
}

echo(osszeutkozes(Aszteroida(101), Urhajo(300)))
echo(osszeutkozes(Aszteroida(10), Urhajo(10)))
    }

Kimenet:

Nagy bumm
a/s

A Raku, a Perl-hez hasonlóan, más nyelvek bevált ötleteit használja, és a típusrendszerek megmutatták, hogy meggyőző előnyöket kínálnak a fordítóoldali kódelemzésben és a felhasználóoldali szemantikában a többszörös feladás révén.

Mind multimetódusokkal, mind multiszubokkal rendelkezik. Mivel a legtöbb operátor szubrutin, többszörösen feladott operátorokkal is rendelkezik.

A szokásos típuskorlátozások mellett rendelkezik where-korlátozásokkal is, amelyek lehetővé teszik nagyon speciális szubrutinok elkészítését.

subset Mass of Real where 0 ^..^ Inf; 
role Stellar-Object {
    has Mass $.mass is required;
    method name () returns Str {...};
}
class Asteroid does Stellar-Object {
    method name () { 'an asteroid' }
}
class Spaceship does Stellar-Object {
    has Str $.name = 'some unnamed spaceship';
}
my Str @destroyed = < obliterated destroyed mangled >;
my Str @damaged = « damaged 'collided with' 'was damaged by' »;

# A numerikus összehasonlító operátorokhoz több jelölést adunk, mivel numerikusan hasonlítjuk össze őket,
# de nincs értelme, hogy az objektumokat numerikus típusra kényszerítsük.
# ( Ha mégis, akkor nem feltétlenül kellene hozzáadni ezeket az operátorokat.)
# Ugyanígy definiálhattunk volna teljesen új operátorokat is.
multi sub infix:« <=> » ( Stellar-Object:D $a, Stellar-Object:D $b ) { $a.mass <=> $b.mass }
multi sub infix:« < » ( Stellar-Object:D $a, Stellar-Object:D $b ) { $a.mass < $b.mass }
multi sub infix:« > » ( Stellar-Object:D $a, Stellar-Object:D $b ) { $a.mass > $b.mass }
multi sub infix:« == » ( Stellar-Object:D $a, Stellar-Object:D $b ) { $a.mass == $b.mass }

# Definiáljunk egy új multi diszpécsert, és adjunk hozzá néhány típuskorlátozást a paraméterekhez.
# Ha nem definiálnánk, akkor egy általánosat kaptunk volna, amiben nincsenek megkötések.
proto sub collide ( Stellar-Object:D $, Stellar-Object:D $ ) {*}

# A típusokat itt nem kell megismételni, mivel ugyanazok, mint a prototípusban.
# A 'where' megkötés technikailag csak a $b-re vonatkozik, nem az egész aláírásra.
# Figyeljük meg, hogy a 'where' kényszer a korábban hozzáadott `<` operátorjelöltet használja.
multi sub collide ( $a, $b where $a < $b ) {
    say "$a.name() was @destroyed.pick() by $b.name()";
}
multi sub collide ( $a, $b where $a > $b ) {
    # redispatch to the previous candidate with the arguments swapped
    samewith $b, $a;
}

# Ennek az első kettő után kell következnie, mert a többinek 
# "where" megkötései vannak, amelyeket a sorrendben kell 
# ellenőrizni, ahogyan az aláírások íródtak. ( Ez az egy mindig egyezne.)
multi sub collide ( $a, $b ) {
    # randomize the order
    my ($n1, $n2) = ( $a.name, $b.name ).pick(*);
    say "$n1 @damaged.pick() $n2";
}

# A következő két jelölt bárhol lehet a proto után,
# mert speciálisabb típusokkal rendelkeznek, mint az előző három.

# Ha a hajók tömege nem egyenlő, akkor az első két jelölt közül az egyiket hívják ki.
multi sub collide ( Spaceship $a, Spaceship $b where $a == $b ){
    my ($n1, $n2) = ( $a.name, $b.name ).pick(*);
    say "$n1 collided with $n2, and both ships were ",
    ( @destroyed.pick, 'left damaged' ).pick;
}

# Az attribútumokat az aláíráson belül változókba csomagolhatja.
# Akár egy megkötés is lehet rájuk `(:mass($a) where 10)`.
multi sub collide ( Asteroid $ (:mass($a)), Asteroid $ (:mass($b)) ){
    say "két aszteroida összeütközött és egy nagyobb tömegű aszteroidává egyesült { $a + $b }";
}

my Spaceship $Enterprise .= new(:mass(1),:name('The Enterprise'));
collide Asteroid.new(:mass(.1)), $Enterprise;
collide $Enterprise, Spaceship.new(:mass(.1));
collide $Enterprise, Asteroid.new(:mass(1));
collide $Enterprise, Spaceship.new(:mass(1));
collide Asteroid.new(:mass(10)), Asteroid.new(:mass(5));

Nyelvek bővítése többszörös feladású könyvtárakkal

szerkesztés

JavaScript

szerkesztés

Azokban a nyelvekben, amelyek nem támogatják a többszörös feladást a nyelvi definíció vagy a szintaktika szintjén, gyakran lehetséges a többszörös feladást könyvtár bővítéssel hozzáadni. A JavaScript és a TypeScript nem támogatja a többszörös metódusokat szintaktikai szinten, de lehetőség van többszörös feladás hozzáadására egy könyvtáron keresztül. A multimethod csomag [14]például többszörös feladású, általános függvények implementációját biztosítja.

Dinamikusan gépelt változat JavaScriptben:

import { multi, method } from '@arrows/multimethod'

class Asteroid {}
class Spaceship {}

const collideWith = multi(
  method([Asteroid, Asteroid], (x, y) => {
    // asztreoida asztreoidával való ütközésével foglalkozik
  }),
  method([Asteroid, Spaceship], (x, y) => {
    // asztreoida űrhajóval való ütközésével foglalkozik
  }),
  method([Spaceship, Asteroid], (x, y) => {
    // űrhajó asztreoidával való ütközésével foglalkozik
  }),
  method([Spaceship, Spaceship], (x, y) => {
    // űrhajó űrhajóval való ütközésével foglalkozik
  }),
)

Statikusan gépelt verzió TypeScriptben:

import { multi, method, Multi } from '@arrows/multimethod'

class Asteroid {}
class Spaceship {}

type CollideWith = Multi & {
  (x: Asteroid, y: Asteroid): void
  (x: Asteroid, y: Spaceship): void
  (x: Spaceship, y: Asteroid): void
  (x: Spaceship, y: Spaceship): void
}

const collideWith: CollideWith = multi(
  method([Asteroid, Asteroid], (x, y) => {
    // asztreoida asztreoidával való ütközésével foglalkozik
  }),
  method([Asteroid, Spaceship], (x, y) => {
    // asztreoida űrhajóval való ütközésével foglalkozik
  }),
  method([Spaceship, Asteroid], (x, y) => {
    // űrhajó asztreoidával való ütközésével foglalkozik
  }),
  method([Spaceship, Spaceship], (x, y) => {
    // űrhajó űrhajóval való ütközésével foglalkozik
  }),
)

A Pythonhoz egy könyvtárbővítmény segítségével többszörös feladás adható hozzá. Például a multimethods.py[15] és a multimethods.py[16] modullal is, amely CLOS-stílusú multimódszereket biztosít a Python számára anélkül, hogy megváltoztatná a nyelv alapvető szintaxisát vagy kulcsszavait.

from multimethods import Dispatch
from game_objects import Asteroid, Spaceship
from game_behaviors import as_func, ss_func, sa_func
collide = Dispatch()
collide.add_rule((Asteroid, Spaceship), as_func)
collide.add_rule((Spaceship, Spaceship), ss_func)
collide.add_rule((Spaceship, Asteroid), sa_func)
def aa_func(a, b):
    """Viselkedés aszteroida aszteroidával való ütközéskor."""
    # ...új viselkedésmód meghatározása...
collide.add_rule((Asteroid, Asteroid), aa_func)
# ...később...
collide(thing1, thing2)

Funkcionálisan ez nagyon hasonló a CLOS példához, de a szintaxis hagyományos Python. Guido van Rossum a Python 2.4 dekorátorok segítségével elkészítette a multimetódusok[17] egyszerűsített szintaxisú mintaimplementációját:

@multimethod(Asteroid, Asteroid)
def collide(a, b):
    """Viselkedés aszteroida aszteroidával való ütközéskor."""
    # ...új viselkedésmód meghatározása...
@multimethod(Asteroid, Spaceship)
def collide(a, b):
    """Viselkedés aszteroida űrhajóval való ütközéskor."""
    # ...új viselkedésmód meghatározása...
# ... egyéb többmódszeres szabályok meghatározása ...

majd a továbbiakban definiálja a multimetódus dekorátort.

A PEAK-Rules csomag a fenti példához hasonló szintaxissal többszörös feladást biztosít.[18] Ezt később a PyProtocols váltotta fel. [19]

A Reg könyvtár támogatja a többszörös- és a predikátum-feladást is. [20]

Többszörös feladás emulálása

szerkesztés

A C nem rendelkezik dinamikus feladással, ezért ezt valamilyen formában manuálisan kell megvalósítani. Gyakran egy enumot használnak egy objektum altípusának azonosítására. A dinamikus feladás úgy végezhető el, hogy ezt az értéket megkeressük egy függvénymutató elágazási táblázatban. Íme egy egyszerű példa C nyelven:

typedef void (*CollisionCase)(void);

void collision_AA(void) { /* handle Asteroid-Asteroid collision  */ };
void collision_AS(void) { /* handle Asteroid-Spaceship collision */ };
void collision_SA(void) { /* handle Spaceship-Asteroid collision */ };
void collision_SS(void) { /* handle Spaceship-Spaceship collision*/ };

typedef enum {
    THING_ASTEROID = 0,
    THING_SPACESHIP,
    THING_COUNT /* 
nem maga a dolog típusa, hanem a dolgok számának megtalálására használják. */
} Thing;

CollisionCase collisionCases[THING_COUNT][THING_COUNT] = {
    {&collision_AA, &collision_AS},
    {&collision_SA, &collision_SS}
};

void collide(Thing a, Thing b) {
    (*collisionCases[a][b])();
}

int main(void) {
    collide(THING_SPACESHIP, THING_ASTEROID);
}

A C Object System könyvtárral a C támogatja a CLOS-hoz hasonló dinamikus feladást. [21]Teljesen bővíthető, és nincs szükség a metódusok kézi kezelésére. A dinamikus üzeneteket (metódusokat) a COS diszpécser diszpécserével küldi el, ami gyorsabb, mint az Objective-C. Íme egy példa a COS-ban:

#include <stdio.h>
#include <cos/Object.h>
#include <cos/gen/object.h>

// osztályok

defclass (Asteroid)
// adattagok
endclass

defclass (Spaceship)
// adattagok
endclass

// generikusok

defgeneric (_Bool, collide_with, _1, _2);

// multimetódusok

defmethod (_Bool, collide_with, Asteroid, Asteroid)
 // asztreoida asztreoidával való ütközésével foglalkozik
endmethod

defmethod (_Bool, collide_with, Asteroid, Spaceship)
 // asztreoida űrhajóval való ütközésével foglalkozik
endmethod

defmethod (_Bool, collide_with, Spaceship, Asteroid)
 // űrhajó asztreoidával való ütközésével foglalkozik
endmethod

defmethod (_Bool, collide_with, Spaceship, Spaceship)
 // űrhajó űrhajóval való ütközésével foglalkozik
endmethod

// használati példa

int main(void)
{
  OBJ a = gnew(Asteroid);
  OBJ s = gnew(Spaceship);

  printf("<a,a> = %d\n", collide_with(a, a));
  printf("<a,s> = %d\n", collide_with(a, s));
  printf("<s,a> = %d\n", collide_with(s, a));
  printf("<s,s> = %d\n", collide_with(s, s));

  grelease(a);
  grelease(s);
}

2021-től a C++ natívan csak az egyszeri feladást támogatja, bár Bjarne Stroustrup (és munkatársai) 2007-ben javasolták a multimetódusok (többszörös feladás) hozzáadását.[22] A korlátok megkerülésének módszerei analógok: vagy a visitor minta, vagy a dynamic cast, vagy egy könyvtár használata:

 // Példa a futásidejű típus-összehasonlítás használatára dynamic_cast segítségével

 struct Thing {
     virtual void collideWith(Thing& other) = 0;
 };

 struct Asteroid : Thing {
     void collideWith(Thing& other) {
         // dynamic_cast egy pointer típusra NULL-t ad vissza, ha az átvitel sikertelen.
         // (a dinamikus_cast referenciatípusra való átvitel hiba esetén kivételt dobna)
         if (auto asteroid = dynamic_cast<Asteroid*>(&other)) {
             // Aszteroida-aszteroida ütközés kezelése
         } else if (auto spaceship = dynamic_cast<Spaceship*>(&other)) {
             // Aszteroida-űrhajó ütközés kezelése
         } else {
             // alapértelmezett ütközéskezelés itt
         }
     }
 };

 struct Spaceship : Thing {
     void collideWith(Thing& other) {
         if (auto asteroid = dynamic_cast<Asteroid*>(&other)) {
             // kezeli az űrhajó-aszteroida ütközést
         } else if (auto spaceship = dynamic_cast<Spaceship*>(&other)) {
             // kezeli az űrhajó-űrhajó ütközést
         } else {
             // alapértelmezett ütközéskezelés itt
         }
     }
 };

vagy pointer-to-method keresési táblázat:

#include <cstdint>
#include <typeinfo>
#include <unordered_map>

class Thing {
  protected:
    Thing(std::uint32_t cid) : tid(cid) {}
    const std::uint32_t tid; // type id

    typedef void (Thing::*CollisionHandler)(Thing& other);
    typedef std::unordered_map<std::uint64_t, CollisionHandler> CollisionHandlerMap;

    static void addHandler(std::uint32_t id1, std::uint32_t id2, CollisionHandler handler) {
        collisionCases.insert(CollisionHandlerMap::value_type(key(id1, id2), handler));
    }
    static std::uint64_t key(std::uint32_t id1, std::uint32_t id2) {
        return std::uint64_t(id1) << 32 | id2;
    }

    static CollisionHandlerMap collisionCases;

  public:
    void collideWith(Thing& other) {
        auto handler = collisionCases.find(key(tid, other.tid));
        if (handler != collisionCases.end()) {
            (this->*handler->second)(other); // pointer-to-method call
        } else {
            // alapértelmezett ütközéskezelés itt
        }
    }
};

class Asteroid: public Thing {
    void asteroid_collision(Thing& other)   { /*Aszteroida-aszteroida ütközés kezelése*/ }
    void spaceship_collision(Thing& other)  { /*Aszteroida-űrhajó ütközés kezelése*/}

  public:
    Asteroid(): Thing(cid) {}
    static void initCases();
    static const std::uint32_t cid;
};

class Spaceship: public Thing {
    void asteroid_collision(Thing& other)   { /*űrhajó-aszteroida ütközés kezelése*/}
    void spaceship_collision(Thing& other)  { /*űrhajó-űrhajó ütközés kezelése*/}

  public:
    Spaceship(): Thing(cid) {}
    static void initCases();
    static const std::uint32_t cid; // class id
};

Thing::CollisionHandlerMap Thing::collisionCases;
const std::uint32_t Asteroid::cid = typeid(Asteroid).hash_code();
const std::uint32_t Spaceship::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(&Spaceship::asteroid_collision));
    addHandler(cid, cid, CollisionHandler(&Spaceship::spaceship_collision));
}

int main() {
    Asteroid::initCases();
    Spaceship::initCases();

    Asteroid  a1, a2;
    Spaceship s1, s2;

    a1.collideWith(a2);
    a1.collideWith(s1);

    s1.collideWith(s2);
    s1.collideWith(a1);
}

A yomm2 könyvtár[23] a nyílt multimódszerek gyors, ortogonális megvalósítását biztosítja.

A nyílt metódusok deklarálásának szintaxisát egy natív C++ implementációra vonatkozó javaslat ihlette. A könyvtár megköveteli, hogy a felhasználó regisztrálja az összes virtuális argumentumként használt osztályt (és azok alosztályait), de nem igényel semmilyen módosítást a meglévő kódban. A metódusok közönséges inline C++ függvényekként vannak implementálva; túlterhelhetők és átadhatók mutatóval. A virtuális argumentumok száma nincs korlátozva, és tetszőlegesen keverhetők nem virtuális argumentumokkal.

A könyvtár többféle technika kombinációját használja (tömörített elosztótáblák, tökéletes integer hash), hogy a metódushívásokat állandó idő alatt valósítsa meg, miközben csökkenti a memóriahasználatot. Egyetlen virtuális argumentummal rendelkező nyitott metódus hívásának elküldése csak 15-30%-kal több időt vesz igénybe, mint egy közönséges virtuális tagfüggvény hívása, ha egy modern optimalizáló fordítót használunk.

Az Aszteroidák példája a következőképpen valósítható meg:

#include <yorel/yomm2/cute.hpp>

using yorel::yomm2::virtual_;

class Thing {
  public:
    virtual ~Thing() {}
    // ...
};

class Asteroid : public Thing {
    // ...
};

class Spaceship : public Thing {
    // ...
};

register_class(Thing);
register_class(Spaceship, Thing);
register_class(Asteroid, Thing);

declare_method(void, collideWith, (virtual_<Thing&>, virtual_<Thing&>));

define_method(void, collideWith, (Thing& left, Thing& right)) {
    // alapértelmezett ütközéskezelés itt
}

define_method(void, collideWith, (Asteroid& left, Asteroid& right)) {
    // Aszteroida-aszteroida ütközés kezelése
}

define_method(void, collideWith, (Asteroid& left, Spaceship& right)) {
    // Aszteroida-űrhajó ütközés kezelése
}

define_method(void, collideWith, (Spaceship& left, Asteroid& right)) {
    // űrhajó-aszteroida ütközés kezelése
}

define_method(void, collideWith, (Spaceship& left, Spaceship& right)) {
    // űrhajó-űrhajó ütközés kezelése
}

int main() {
    yorel::yomm2::update_methods();

    Asteroid  a1, a2;
    Spaceship s1, s2;

    collideWith(a1, a2);
    collideWith(a1, s1);

    collideWith(s1, s2);
    collideWith(s1, a1);
}

Stroustrup megemlíti a The Design and Evolution of C++ című könyvében , hogy tetszett neki a multimetódusok koncepciója, és fontolgatta annak C++-ban való megvalósítását, de azt állítja, hogy nem tudott hatékony mintaimplementációt találni (a virtuális függvényekhez hasonlóan), és megoldani néhány lehetséges típus-többértelműségi problémát. Ezután azt állítja, hogy bár a funkciót továbbra is jó lenne megvalósítani, ez megközelítőleg megvalósítható a fenti C/C++ példában vázolt dupla feladással vagy típusalapú keresőtáblával, így a jövőbeli nyelvi revíziókban alacsony prioritású funkciónak számít.[24]

2021-től a D - sok más objektumorientált programozási nyelvhez hasonlóan - natívan csak egyszeri feladást támogat. Lehetőség van azonban arra, hogy a D-ben könyvtári függvényként emuláljuk a nyitott multimetódusokat.[25]

// Deklaráció
Matrix plus(virtual!Matrix, virtual!Matrix);

// A két DenseMatrix objektum felülírása
@method
Matrix _plus(DenseMatrix a, DenseMatrix b)
{
  const int nr = a.rows;
  const int nc = a.cols;
  assert(a.nr == b.nr);
  assert(a.nc == b.nc);
  auto result = new DenseMatrix;
  result.nr = nr;
  result.nc = nc;
  result.elems.length = a.elems.length;
  result.elems[] = a.elems[] + b.elems[];
  return result;
}

// A két DiagonalMatrix objektum felülírása
@method
Matrix _plus(DiagonalMatrix a, DiagonalMatrix b)
{
  assert(a.rows == b.rows);
  double[] sum;
  sum.length = a.elems.length;
  sum[] = a.elems[] + b.elems[];
  return new DiagonalMatrix(sum);
}

Egy olyan nyelvben, ahol csak egyszeres feladás van, mint például a Java, a többszörös feladás az egyszeres feladás több szintjével emulálható:

interface Collideable {
    void collideWith(final Collideable other);

    /* Ezeknek a metódusoknak más nevekre lenne szükségük egy olyan nyelvben, ahol nincs metódus-túlterhelés. */
    void collideWith(final Asteroid asteroid);
    void collideWith(final Spaceship spaceship);
}

class Asteroid implements Collideable {
    public void collideWith(final Collideable other) {
        // Hívja a collideWith-t a másik objektummal
        other.collideWith(this);
   }

    public void collideWith(final Asteroid asteroid) {
        // Aszteroida-aszteroida ütközés kezelése
    }

    public void collideWith(final Spaceship spaceship) {
        // Aszteroida-űrhajó ütközés kezelése
    }
}

class Spaceship implements Collideable {
    public void collideWith(final Collideable other) {
        // Hívja a collideWith-t a másik objektummal
        other.collideWith(this);
    }

    public void collideWith(final Asteroid asteroid) {
        // űrhajó-aszteroida ütközés kezelése
    }

    public void collideWith(final Spaceship spaceship) {
        // űrhajó-űrhajó ütközés kezelése
    }
}

A futásidejű instanceof ellenőrzések az egyik vagy mindkét szinten is használhatók.

Programozási nyelvek támogatása

szerkesztés

Elsődleges paradigma

szerkesztés

Bővítmények

szerkesztés

Lásd még

szerkesztés
  • Predicate dispatch

Hivatkozások

szerkesztés

 

  1. Contemporary Computing: Second International Conference, IC3 2010, Noida, India, August 9–11, 2010. Proceedings. Springer (2010. július 26.). ISBN 9783642148248 
  2. a b c d e f g h i j k Multiple dispatch in practice, OOPSLA '08. Nashville, TN, USA: ACM, 563–582. o.. DOI: 10.1145/1449764.1449808 (2008). ISBN 9781605582153 
  3. a b c d Bezanson (2017. február 7.). „Julia: A fresh approach to numerical computing”. SIAM Review 59 (1), 65–98. o. DOI:10.1137/141000671.  
  4. (1995) „A calculus for overloaded functions with subtyping”. Information and Computation 117 (1), 115–135. o. DOI:10.1006/inco.1995.1033. (Hozzáférés: 2013. április 19.)  
  5. Castagna, Giuseppe. Object-Oriented Programming: A Unified Foundation, Progress in Theoretical Computer Science. Birkhäuser, 384. o. (1996). ISBN 978-0-8176-3905-1 
  6. Castagna, Giuseppe (1995). „Covariance and contravariance: conflict without a cause”. ACM Transactions on Programming Languages and Systems 17 (3), 431–447. o. DOI:10.1145/203095.203096.  
  7. Bruce (1995). „On binary methods”. Theory and Practice of Object Systems 1 (3), 221–242. o. DOI:10.1002/j.1096-9942.1995.tb00019.x. (Hozzáférés: 2013. április 19.)  
  8. Groovy - Multi-methods
  9. NGSLANG(1) NGS User Manual. ngs-lang.org. (Hozzáférés: 2019. október 1.)
  10. @arrows/multimethod Multiple dispatch in JavaScript/TypeScript with configurable dispatch resolution by Maciej Cąderek.
  11. multimethods.py Archiválva 2005. március 9-i dátummal a Wayback Machine-ben., Multiple dispatch in Python with configurable dispatch resolution by David Mertz, et al.
  12. Five-minute Multimethods in Python
  13. PEAK-Rules 0.5a1.dev. Python Package Index. (Hozzáférés: 2014. március 21.)
  14. PyProtocols. Python Enterprise Application Kit. (Hozzáférés: 2019. április 26.)
  15. Reg. Read the docs. (Hozzáférés: 2019. április 26.)
  16. Report on language support for Multi-Methods and Open-Methods for C ++, 2007. március 11. „Multiple dispatch – the selection of a function to be invoked based on the dynamic type of two or more arguments – is a solution to several classical problems in object-oriented programming.”
  17. yomm2, Fast, Orthogonal Open Multi-Methods for C++ by Jean-Louis Leroy.
  18. Stroustrup, Bjarne. Section 13.8, The Design and Evolution of C++. Indianapolis, IN, U.S.A: Addison Wesley (1994). ISBN 978-0-201-54330-8 
  19. C Object System: A framework that brings C to the level of other high level programming languages and beyond: CObjectSystem/COS, 2019. február 19.
  20. a b Methods. The Julia Manual. Julialang. [2016. július 17-i dátummal az eredetiből archiválva]. (Hozzáférés: 2014. május 11.)
  21. openmethods, Open Multi-Methods for D by Jean-Louis Leroy.
  22. Multimethods in C# 4.0 With 'Dynamic'. (Hozzáférés: 2009. augusztus 20.)
  23. Cecil Language. (Hozzáférés: 2008. április 13.)
  24. Multimethods in Clojure. (Hozzáférés: 2008. szeptember 4.)

Forráshivatkozás-hiba: a <references> címkében definiált „valami1” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami2” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami3” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami4” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami5” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami6” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami7” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami8” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami9” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami10” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami11” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami12” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami13” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami14” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami15” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami16” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami17” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami18” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami19” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami20” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami21” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami22” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami23” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami24” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami25” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami26” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami27” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami28” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami29” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami30” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami31” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami32” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami33” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami34” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami35” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami36” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami37” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami38” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami39” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami40” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami41” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami42” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami43” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami44” nevű <ref> címke nem szerepel a szöveg korábbi részében.
Forráshivatkozás-hiba: a <references> címkében definiált „valami45” nevű <ref> címke nem szerepel a szöveg korábbi részében.

Külső hivatkozások

szerkesztés

[[Kategória:Python-kódpéldák]] [[Kategória:Java-kódpéldák]]