Polimorfizmus (informatika)

A programozási nyelvekben és a típuselméletben a polimorfizmus (a görög πολύς, sok és a μορφή, alak szavakból) egy egységes interfészre utal, amit különböző típusok valósítanak meg.[1] A polimorf típuson végzett műveletek több különböző típus értékeire alkalmazhatók.[2] Polimorfizmus többféleképpen is megvalósítható:

  • Ad hoc polimorfizmus: Egy függvénynek sok különböző implementációja van, amelyeket egyenként specifikálnak néhány különböző típus és kombinációja számára. Megvalósítható túlterheléssel.
  • Paraméteres polimorfizmus: A kódot általánosan írják meg különböző típusok számára, és alkalmazható az összes típusra, amely megfelel bizonyos, a kódban előre megadott feltételeknek. Objektumorientált környezetben sablonnak vagy generikusnak nevezik. Funkcionális programozási környezetben egyszerűen polimorfizmusnak hívják.
  • Altípusosság: A név több különböző osztály példányait jelöli, amelyeknek a függvényt deklaráló közös őse van.[3] Objektumorientált környezetben többnyire erre gondolnak, amikor polimorfizmusról beszélnek.

A paraméteres és az altípusos polimorfizmus kapcsolata elvezet a variancia és a korlátos minősítés, más néven korlátozott polimorfizmus fogalmához.

Története

Először az angol Christopher Strachey írta le 1967-ben az ad hoc és a paraméteres polimorfizmust előadásjegyzeteiben.[4] Peter Wegner és Luca Cardelli egy 1985-ben megjelent cikkükben bevezette a beágyazási polimorfizmust az altípusosság és az öröklődés modellezésére. Maga az altípusosság implementációja megelőzte ezt a cikket,[2] a Simula 1967 programnyelv már tartalmazott öröklődést és altípusosságot.

Típusai

Az implicit típuskonverziót szintén a polimorfizmus egy fajtájának tekintik, ez a kényszerítési polimorfizmus.[2][5]

Ad hoc polimorfizmus

Chris Strachey az ad hoc polimorfizmus elnevezést arra az esetre használta, amikor az argumentumok különböző típusúak lehetnek, és a függvény ennek megfelelően különféleképpen viselkedhet, amit túlterheléssel lehet elérni. Ha több különböző típusra is ugyanúgy viselkedik a függvény, akkor érdemes a másik két polimorfizmus egyikére áttérni. Itt az ad hoc kifejezés nem pejoratív, hanem arra utal, hogy nem következik a típusrendszerből. Az alábbi Pascal/Delphi példában az Add függvény a hívások szerint különféle típusokra működik, de a fordító minden tekintetben külön függvényként kezeli őket.

program Adhoc;

function Add(x, y : Integer) : Integer;
begin
    Add := x + y
end;

function Add(s, t : String) : String;
begin
    Add := Concat(s, t)
end;

begin
    Writeln(Add(1, 2));                   (* Prints "3"             *)
    Writeln(Add('Hello, ', 'World!'));    (* Prints "Hello, World!" *)
end.

Dinamikusan típusozott nyelvekben a helyzet még bonyolultabb, mert csak futásidőben lehet meghatározni, hogy melyik függvényt kell meghívni.

Paraméteres polimorfizmus

A parametrikus polimorfizmus jellemzője, hogy a függvényt egyszer kell megírni, és az minden típusra egységesen fog működni. A függvény paramétereinek típusát a függvény használatakor paraméterben kell megadni, innen a paraméteres név. Szokták generikusnak is hívni, az egységes viselkedés miatt. Használata a teljes típusbiztonság megőrzésével növeli a nyelv kifejezőerejét.

A paraméteres polimorfizmus nemcsak függvényekre, hanem adattípusokra is értelmezhető, tehát adattípusokra is lehetnek paraméteresek, más néven generikusok. A generikus típusok polimorfikus adattípusok néven is megtalálhatók.

A parametrikus polimorfizmus nagyon gyakori a funkcionális programozásban, emiatt gyakran csak polimorfizmusnak nevezik. Az alábbi Haskell példa egy paraméteres polimorf lista adattípust mutat be, két paraméteresen polimorf függvénnyel.

data List a = Nil | Cons a (List a)

length :: List a -> Integer
length Nil         = 0
length (Cons x xs) = 1 + length xs

map :: (a -> b) -> List a -> List b
map f Nil         = Nil
map f (Cons x xs) = Cons (f x) (map f xs)

Objektumorientált nyelvekben kevésbé gyakori, de legtöbbjükben szintén van lehetőség paraméteres polimorfizmusra. Ezt C++-ban és D-ben template-ek, Javában genericek biztosítják. Az alábbi példa C#-ban íródott:

class List<T> {
    class Node<T> {
        T elem;
        Node<T> next;
    }
    Node<T> head;
    int length() { ... }
}

List<B> map(Func<A, B> f, List<A> xs) {
    ...
}

John C. Reynolds és később Jean-Yves Girard formálisan ezt a jelölést vezette be a lambda-kalkulusba, és a polimorfikus lambda-kalkulust F-rendszernek nevezte. A paraméteres polimorfikus függvények inkább az adatok formájával, mint értékükkel dolgozhatnak, általánosan, típusfüggetlen módon. Igazi képességeik metaprogramozással bontakoztathatók ki.

Altípusosság

Egyes nyelvekben az altípusosság használható a polimorfizmus korlátozására. Ezekben a nyelvekben az altípusosság lehetővé teszi, hogy egy függvény egy bizonyos T típusú objektumot használjon, de ha S altípusa T-nek, akkor a függvény S típusú objektumokra is használható a Liskov-féle helyettesíti elv szerint. Az altípus jelölése néha S <: T. Megfordítva, T szupertípusa S-nek, ennek jele T :> S. Az altípusos polimorfizmus rendszerint dinamikus.

A következő példában a kutya és a macska az állatok altípusa. A letsHear() eljárás állatot fogad, de altípusos objektumokkal is működik.

abstract class Animal {
    abstract String talk();
}

class Cat extends Animal {
    String talk() {
        return "Meow!";
    }
}

class Dog extends Animal {
    String talk() {
        return "Woof!";
    }
}

void letsHear(final Animal a) {
    println(a.talk());
}

int main() {
    letsHear(new Cat());
    letsHear(new Dog());
}

A következő példában Number, Rational, és Integer típusok úgy, hogy Number :> Rational és Number :> Integer. Egy függvény, aminek paramétere Number, úgy működik Integer vagy Rational paraméterrel, mint Number paraméterrel. Az aktuális típus elrejthető a felhasználó elől, és objektumazonossággal férhető hozzá. Valójában, ha a Number típus absztrakt, akkor nem is lehetnek olyan objektumok, amelyek aktuális típusa Number. Lásd: absztrakt osztály, absztrakt adattípus. Ez a típushierarchia számtoronyként is ismert a Scheme programozási nyelvből, és rendszerint még több típusból áll.

Az objektumorientált nyelvek az altípusos polimorfizmust öröklődéssel valósítják meg. Tipikus implementációkban az osztályok tartalmaznak virtuális táblát, ami hivatkozza az osztály interfészének polimorf részét megvalósító függvényeit. Minden objektum tartalmaz egy referenciát erre a táblára, amire mindig szükség van, ha valami egy polimorf függvényt hív. Ez a mechanizmus példa a:

  • Késői kötésre, mert a virtuális függvényhívások csak a híváskor kapcsolódnak;
  • Egyszeri küldésre, mivel a virtuális függvényhívások csak az első argumentumuk virtuális tábláját veszik figyelembe, így a többi argumentum dinamikus típusa nincs figyelembe véve.

Néhány objektumrendszer, például a Common Lisp Object System többszörös küldésre is képes, így a hívások az összes argumentumokban polimorfak lesznek.

Politípusosság

Funkcionális környezetben hasonló fogalom a politípusosság, ami még általánosabb, mint a polimorf. Egy ilyen függvényben, habár bizonyos speciális típusokra megadhatók ad hoc esetek, nem tartalmaz ad hoc kombinátort.[6]

Statikus és dinamikus polimorfizmus

A polimorfizmus lehet statikus vagy dinamikus, ezt az implementáció kiválasztása dönti el. Ez statikus, illetve dinamikus kötés néven ismert.

A statikus végrehajtása gyorsabb, mivel nem kell dinamikus kötéseket figyelni, viszont a fordítónak többet kell dolgoznia, a program lassabban és több memóriát igénybe véve fordul. Továbbá megkönnyíti a kód elemzését statikus elemző eszközök többet látnak belőle, és az ember számára is érthetőbb. A dinamikus rugalmasabb, de lassítja a futást, tehetővé teszi a kacsaszerű típusosságot (duck typing). Egy dinamikusan linkelt könyvtár az objektumokat azok pontos típusának ismerete nélkül is képes kezelni.

Általában statikus az ad hoc és a paraméteres polimorfizmus, és dinamikus az altípusos. Ezzel szemben elérhető a statikus altípusosság metaprogramozással, pontosabban CRTP-vel (curiously recurring template pattern).

Jegyzetek

  1. Bjarne Stroustrup: Bjarne Stroustrup's C++ Glossary, 2007. február 19. „polymorphism – providing a single interface to entities of different types.”
  2. a b c (1985. december 1.) „On understanding types, data abstraction, and polymorphism”. ACM Computing Surveys, New York, NY, USA 17 (4), 471–523. o, Kiadó: ACM. DOI:10.1145/6041.6042. ISSN 0360-0300. : "Polymorphic types are types whose operations are applicable to values of more than one type."
  3. Booch, et al 2007 Object-Oriented Analysis and Design with Applications. Addison-Wesley.
  4. Christopher Strachey. Fundamental Concepts in Programming Languages [archivált változat]. Kluwer Academic Publishers. Hozzáférés ideje: 2017. április 27. [archiválás ideje: 2017. augusztus 12.] 
  5. Allen B. Tucker. Computer Science Handbook, Second Edition. Taylor & Francis, 91–. o. (2004. június 28.). ISBN 978-1-58488-360-9 
  6. Ralf Lammel and Joost Visser, "Typed Combinators for Generic Traversal", in Practical Aspects of Declarative Languages: 4th International Symposium (2002), p. 153.