Πολυμορφισμός (υπολογιστές)

Στην πληροφορική, ο πολυμορφισμός είναι ένα χαρακτηριστικό των γλωσσών προγραμματισμού που επιτρέπει το χειρισμό τιμών διαφορετικών τύπων δεδομένων με χρήση μιας ομοιόμορφης διεπαφής. Η έννοια του παραμετρικού πολυμορφισμού εφαρμόζεται τόσο στους τύπους δεδομένων, όσο και στις συναρτήσεις. Μια συνάρτηση που μπορεί να αποτιμηθεί ή να εφαρμοστεί σε τιμές διαφορετικών τύπων είναι γνωστή ως πολυμορφική συνάρτηση. Ένας τύπος δεδομένων που εμφανίζεται ως γενικευμένου τύπου (π.χ. μια λίστα με στοιχεία οποιουδήποτε τύπου) ονομάζεται πολυμορφικός τύπος δεδομένων, ομοίως με το γενικευμένο τύπο από τον οποίο παράγονται οι εξειδικεύσεις.

Υπάρχουν αρκετά διαφορετικά είδη πολυμορφισμού, δύο από τα οποία περιγράφησαν αρχικά από τον Christopher Strachey το 1967. Αν μια συνάρτηση ορίζει διαφορετικές και πιθανώς ετερογενείς υλοποιήσεις εξαρτώμενες από ένα περιορισμένο εύρος ατομικά ορισμένων τύπων και συνδυασμών, τότε ονομάζεται ad-hoc πολυμορφική. Ο ad-hoc πολυμορφισμός υποστηρίζεται από πολλές γλώσσες με τη χρήση υπερφόρτωσης συναρτήσεων και μεθόδων.

Αν όλος ο κώδικας είναι γραμμένος χωρίς αναφορά συγκεκριμένων τύπων και ως εκ τούτου μπορεί να χρησιμοποιηθεί διαφανώς με οποιοδήποτε αριθμό νέων τύπων, ονομάζεται παραμετρικά πολυμορφικός. Ο John C. Reynolds (και αργότερα ο Jean-Yves Girard) ανέπτυξαν τυπικά την έννοια αυτή του πολυμορφισμού ως μια προέκταση του λάμδα λογισμού (που καλείται πολυμορφικός λάμδα λογισμός, ή Σύστημα F). Ο παραμετρικός πολυμορφισμός υποστηρίζεται ευρέως στις συναρτησιακές γλώσσες προγραμματισμού με στατικά συστήματα τύπων. Στην αντικειμενοστραφή προγραμματιστική κοινότητα, ο προγραμματισμός με χρήση παραμετρικού πολυμορφισού συχνά αποκαλείται γενικός προγραμματισμός.

Στον αντικειμενοστρεφή προγραμματισμό, ο πολυμορφισμός υποτύπων ή πολυμορφισμός συμπερίληψης (inclusion polymorphism) είναι μια έννοια της θεωρίας τύπων στην οποία ένα όνομα μπορεί να ορίζει στιγμιότυπα πολλών διαφορετικών κλάσεων, εφόσον αυτές συνδέονται με κάποια κοινή υπερκλάση. Ο πολυμορφισμός συμπερίληψης εν γένει υποστηρίζεται δια της χρήσης υποτύπων, για παράδειγμα αντικείμενα διαφορετικών τύπων μπορούν να αντικατσταθούν από αντικείμενα άλλου τύπου (του βασικού τους τύπου) κι έτσι είναι διαχειρίσιμα μέσω μιας κοινής διεπαφής. Εναλλακτικά, ο πολυμορφισμός συμπερίληψης μπορεί να επιτευχθεί μέσω του εξαναγκασμού τύπων, γνωστού και ως μετατροπή τύπων (type casting).

Η αλληλεπίδραση μεταξύ του παραμετρικού πολυμορφισμού και της χρήσης υποτύπων οδηγεί στις έννοιες των φραγμένων ποσοδεικτών (bounded quantification) και της συνδιακύμανσης (covariance) και αντιδιακύμανσης (contravariance) (πολικότητας) των κατασκευαστών τύπων (type constructors).

Ιστορία

Μορφές Πολυμορφισμού

Ad-hoc Πλυμορφισμός 

Ο Chris Strachey επέλεξε τον όρο ad-hoc πολυμορφισμός για να αναφερθεί στις πολυμορφικές συναρτήσεις που μπορούν να εφαρμοστούν σε ορίσματα διαφορετικών τύπων, οι οποίες όμως συμπεριφέρονται διαφορετικά αναλόγως του τύπου των ορισμάτων στα οποία εφαρμόζονται (επίσης γνωστό ως υπερφόρτωση συναρτήσεων ή υπερφόρτωση τελεστών). Ο όρος "ad hoc" σε αυτό το περιβάλλον δεν έχει σκοπό να είναι υποτιμικός, απλά αναφέρεται στο γεγονός ότι αυτός ο τύπος πολυμορφισμού δεν είναι ένα έμφυτο χαρακτηριστικό του συστήματος τύπων. Στο παρακάτω παράδειγμα, οι συναρτήσεις Add φάινονται να δουλεύουν γενικά σε ποικίλους τύπους όταν κοιτάμε τις κλήσεις, αλλά θεωρούνται δύο εντελώς διαφορετικές συναρτήσεις από το μεταγλωττιστή για όλους τους σκοπούς:

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));
    Writeln(Add('Hello, ', 'World!'));
end.
Στις γλώσσες προγραμματισμού με δυναμικά συστήματα τύπων η κατάσταση μπορεί να είναι πολύ πιο σύνθετη, καθώς η σωστή συνάρτηση που πρέπει να κληθεί μπορεί να αποφασίζεται κατά την εκτέλεση.

Παραμετρικός πολυμορφισμός 

Ο παραμετρικός πολυμορφισμός επιτρέπει σε μια συνάρτηση ή σε ένα τύπο δεδομένων να γραφτεί γενικά, ώστε να μπορεί να χειριστεί τιμές πανομοιότυπα χωρίς να εξαρτάται από τον τύπο τους. Ο παραμετρικός πολυμορφισμός είναι ένας τρόπος να γίνει μια γλώσσα πιο εκφραστική, ενώ παράλληλα διατηρεί πλήρως την στατική ασφάλεια τύπων.

Ο παραμετρικός πολυμορφισμός είναι διάχυτος στο συναρτησιακό προγραμματισμό, στον οποίο συχνά αναφέρεται ως "πολυμορφισμός". Το παρακάτω παράδειγμα δείχνει ένα παραμετροποιήσιμο τύπο δεδομένων λίστας και δύο παραμετρικά πολυμορφικές συναρτήσεις πάνω του:
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)

Ο παραμετρικός πολυμορφισμός εμφανίζεται και σε διάφορες αντικειμενοστραφείς γλώσσες προγραμματισμού, όπου είναι γνωστός με το όνομα "γενικότητα":

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) {
    ...
}
Κάθε παραμετρικά πολυμορφική συνάρτηση είναι αναγκαστικά περιορισμένη στο τι μπορεί να κάνει, δουλεύοντας στο σχήμα των δεδομένων παρά στην τιμή τους, οδηγώντας στην έννοια της παραμετρικότητας.

Πολυμορφισμός υποτύπων (ή πολυμορφισμός συμπερίληψης) 

Μερικές γλώσσες χρησιμοποιούν την ιδέα των υποτύπων για να περιορίσουν το εύρος τύπων που μπορούν να χρησιμοποιηθούν σε μια συγκεκριμένη περίπτωση παραμετρικού πολυμορφισμού. Σε αυτές τις γλώσσες, ο πολυμορφισμός υποτύπων (μερικές φορές αναφέρεται και ως δυναμικός πολυμορφισμός) επιτρέπει σε μια συνάρτηση να γραφεί ώστε να παίρνει ένα αντικείμενο ενός συγκεκριμένου τύπου T, αλλά επίσης να δουλεύει σωστά αν της σταλεί ένα αντικείμενο που ανήκει σε έναν τύπο S ο οποίος είναι υποτύπος του T (σύμφωνα με την αρχή αντικατάστασης της Liskov). Αυτή η σχέση τύπων μερικές φορές γράφεται S <: T. Αντιστρόφως, ο τύπος T λέγεται ότι είναι υπερτύπος του S, το οποίο γράφεται T :> S.

Για παράδειγμα, αν Αριθμός, Ρητός και Ακέραιος είναι τύποι τέτοιοι ώστε να ισχύει Αριθμός :> Ρητός και Αριθμός :> Ακέραιος, μια συνάρτηση που έχει γραφεί να δέχεται ένα αντικείμενο "Αριθμός" θα δουλέψει εξίσου καλά όταν της δοθεί ένα αντικείμενο "Ακέραιος" ή "Ρητός". Ο πραγματικός τύπος του αντικειμένου μπορεί να κρυφτεί από τον κώδικα-πελάτη με χρήση της τεχνικής του "μαύρου κουτιού" και να έχει πρόσβαση σε αυτό μέσω μιας "ταυτότητας" (identity). Στην πραγματικότητα, αν ο τύπος "Αριθμός" είναι αφηρημένος τύπος, μπορεί να μην είναι δυνατό να δημιουργηθεί ένα αντικείμενο αυτού του τύπου (βλ. αφηρημένος τύπος δεδομένων, αφηρημένη κλάση). Το συγκεκριμένο είδος ιεραρχίας τύπων είναι γνωστό - ιδιαίτερα στα πλαίσια της γλώσσας προγραμματισμού Scheme - ως αριθμητικός πύργος και συνήθως περιλαμβάνει πολλούς περισσότερους τύπους.

Οι αντικειμενοστρεφείς γλώσσες προγραμματισμού προσφέρουν τον πολυμορφισμό υποτύπων χρησιμοποιώντας υποκλάσεις (επίσης γνωστό ως κληρονομικότητα). Σε τυπικές υλοποιήσεις, κάθε κλάση περιέχει αυτό που αποκαλέιται εικονικός πίνακας - ένας πίνακας συναρτήσεων που υλοποιούν το πολυμορφικό κομμάτι της διεπαφής της κλάσης - και κάθε αντικείμενο περιέχει ένα δείκτη στον εικονικό πίνακα της κλάσης, τον οποίο συμβουλεύεται κάθε φορά που καλείται μια πολυμορφική μέθοδος. Ο μηχανισμός αυτός είναι ένα παράδειγμα των:
 Καθυστερημένη δέσμευση (late binding), επειδή οι κλήσεις εικονικών συναρτήσεων δεν είναι "δεμένες" μέχρι τη στιγμή της κλήσης και
 Μοναδική αποστολή (single dispatch) (π.χ. πολυμορφισμός μοναδικού ορίσματος), επειδή οι κλήσεις εικονικών συναρτήσεων "δένουνται" απλά κοιτώντας τον εικονικό πίνακα που παρέχεται από το πρώτο όρισμα (το αντικείμενο this), έτσι οι τρέχοντες τύποι των υπολοίπων ορισμάτων είναι εντελώς άνευ σημασίας.
Τα ίδια ισχύουν και για τα περισσότερα από τα υπόλοιπα δημοφιλή συστήματα αντικειμένων. Κάποια όμως, όπως το CLOS, παρέχουν πολλαπλή αποστολή (multiple dispatch), υπό την οποία οι κλήσεις μεθόδων είναι πολυμορφικές ως προς όλα τα ορίσματα.

Στο παρακάτω παράδειγμα δημιουργούμε τους υποτύπους του τύπο "Animal", "Cat" και "Dog". Η διαδικάσία "write" δέχεται ένα "Animal", αλλά θα δουλέψει εξίσου σωστά αν της δοθεί ένας υποτύπος του.
abstract class Animal {
    String talk();
}

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

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

static void write(Animal a) {
    System.out.println(a.talk());
}

static void main() {
    write(new Cat());
    write(new Dog());
}

Δείτε επίσης 
 Πολυμορφισμός υποτύπων
 Παραμετρικός πολυμορφισμός
 Duck typing για πολυμορφισμό χωρίς (στατικούς) τύπους
 Πολυμορφικός Κώδικας (Ορολογία ιών υπολογιστών)
 Σύστημα F για έναν λ-λογισμό με παραμετρικό πολυμορφισμό
 Εικονική κληρονομικότητα

Αναφορές 
 Luca Cardelli, Peter Wegner. On Understanding Types, Data Abstraction, and Polymorphism, from Computing Surveys, (December, 1985)
 Philip Wadler, Stephen Blott. How to make ad-hoc polymorphism less ad hoc, from Proc. 16th ACM Symposium on Principles of Programming Languages, (January, 1989)
 Christopher Strachey. Fundamental Concepts in Programming Languages, from Higher-Order and Symbolic Computation, (April, 2000; original paper is dated 1967)
 Paul Hudak, John Peterson, Joseph Fasel. A Gentle Introduction to Haskell Version 98.
 Booch, et All. Object-Oriented Analysis and Design with Application.

Περαιτέρω διάβασμα 
 C++ examples of polymorphism
 Objects and Polymorphism (Visual Prolog)
 Object-oriented programming in C

 
Αντικειμενοστρεφής προγραμματισμός