Σύστημα τύπων

Στην επιστήμη υπολογιστών, ως σύστημα τύπων (type system) μπορεί να οριστεί ένα διαχειρίσιμο συντακτικό πλαίσιο για την κατάταξη φράσεων ανάλογα με τις τιμές που υπολογίζουν. Ένα σύστημα τύπων αποδίδει τύπους (types) σε κάθε υπολογισμένη τιμή. Εξετάζοντας τη ροή αυτών των τιμών, ένα σύστημα τύπων πρέπει να αποδείξει ότι δεν πρόκειται να συμβούν σφάλματα τύπων (type errors). Αν και κάθε διαφορετικό σύστημα τύπων ορίζει τι αποτελεί σφάλμα τύπου, γενικά ένα σύστημα τύπων προσπαθεί να εξασφαλίσει ότι λειτουργίες που δέχονται τιμές συγκεκριμένου είδους δε χρησιμοποιούνται με τιμές που δεν πρέπει.
Ένας μεταγλωττιστής μπορεί να χρησιμοποιήσει το στατικό τύπο μιας τιμής για να βελτιστοποιήσει τον τρόπο με τον οποίο αυτή θα αποθηκευθεί και τι αλγόριθμοι μπορούν να χρησιμοποιηθούν σε αυτή. Για παράδειγμα, σε πολλούς μεταγλωττιστές της C, ο τύπος δεδομένων "αριθμός κινητής υποδιαστολής" ("float") αναπαρίσταται από 32 bits, σύμφωνα με το πρότυπο κινητής υποδιαστολής απλής ακρίβειας κατά IEEE. Σε αυτήν την περίπτωση η C χρησιμοποιεί λειτουργίες ειδικά για αριθμούς κινητής υποδιαστολής σε αυτές τις τιμές (πρόσθεση αριθμών κινητής υποδιαστολής, πολλαπλασιασμός, κλπ).
Το πόσο λεπτομερείς είναι οι περιορισμοί των τύπων και ο τρόπος με τον οποίο αποτιμώνται επηρεάζει την τυποποίηση (typing) μιας γλώσσας. Μια γλώσσα προγραμματισμού μπορεί επιπλέον να αντιστοιχίζει λειτουργίες διαφορετικές υλοποιήσεις αλγορίθμων σε διαφορετικούς τύπους στην περίπτωση του πολυμορφισμού τύπων (type polymorphism). Θεωρία τύπων (type theory) είναι η μελέτη των συστημάτων τύπων, αν και σημαντικό μέρος των συστημάτων τύπων που χρησιμοποιούνται στην πράξη προέρχονται από έρευνα στην αρχιτεκτονική υπολογιστών, την υλοποίηση μεταγλωττιστών και τη σχεδίαση γλωσσών.

Έλεγχος τύπων

Η επαλήθευση και η εφαρμογή των περιορισμών των τύπων - έλεγχος τύπων (type checking) – μπορεί να συμβεί είτε στο χρόνο μεταγλώττισης (στατικός έλεγχος), είτε στο χρόνο εκτέλεσης (δυναμικός έλεγχος). Αν ο ορισμός μιας γλώσσας απαιτεί ισχυρούς κανόνες τύπων (συνήθως επιτρέποντας μόνο αυτόματες μετατροπές τύπων που δε χάνουν πληροφορία), αυτή μπορεί να ονομαστεί ισχυρά τυποποιήσιμη (strongly typed), σε αντίθετη περίπτωση, ασθενώς τυποποιήσιμη (weakly typed). Οι όροι αυτοί δε χρησιμοποιούνται με αυστηρό τρόπο.

Δυναμικοί τύποι

Μια γλώσσα προγραμματισμού λέγεται ότι έχει δυναμικούς τύπους όταν το μεγαλύτερο μέρος του ελέγχου τύπων της εκτελείται στο χρόνο εκτέλεσης αντί του χρόνου μεταγλώττισης. Στους δυναμικούς τύπους, οι τιμές έχουν τύπους αλλά οι μεταβλητές δεν έχουν με αποτέλεσμα μια μεταβλητή να μπορεί να αναφέρεται σε τιμές κάθε είδους. Οι γλώσσες με δυναμικούς τύπους περιλαμβάνουν την APL, την Erlang, τη Groovy, τη JavaScript, τη Lisp, τη Lua, αυτές των MATLAB/GNU Octave, την Perl (όσον αφορά τους τύπου που ορίζονται από το χρήστη αλλά όχι τους ενσωματωμένους), την PHP, την Prolog, την Python, τη Ruby, τη Smalltalk και την Tcl. Οι δυναμικοί τύποι μπορούν να είναι πιο ευέλικτοι από τους στατικούς τύπους (π.χ. επιτρέποντας σε ένα πρόγραμμα να παράγει τύπους και λειτουργίες με βάση δεδομένα στο χρόνο εκτέλεσης), αλλά εις βάρος λιγότερων εγγυήσεων εκ των προτέρων. Αυτό οφείλεται στο ότι μια γλώσσα με δυναμικούς τύπους δέχεται και προσπαθεί να εκτελέσει ορισμένα προγράμματα που θεωρούνται μη έγκυρα από τον στατικό ελεγκτή τύπων.
Οι δυναμικοί τύποι μπορεί να προκαλέσουν σφάλματα τύπων στο χρόνο εκτέλεσης: μια τιμή μπορεί να έχει τύπο που δεν προβλέπεται στο χρόνο εκτέλεσης, και να πρέπει να εκτελεστεί μια λειτουργία σε αυτήν που να μην έχει νόημα. Αυτή η λειτουργία μπορεί να συμβεί σε σημείο μεταγενέστερο από αυτό στο οποίο έγινε το προγραμματιστικό λάθος - εκεί δηλαδή που δεδομένα λάθος τύπου περάστηκαν σε κάποιον κώδικα που δε θα έπρεπε. Αυτό δυσχεραίνει την εύρεση τέτοιων σφαλμάτων.
Οι έλεγχοι στο χρόνο εκτέλεσης των συστημάτων των γλωσσών με δυναμικούς τύπους μπορούν να είναι πιο πολύπλοκοι από αυτούς των γλωσσών με στατικούς τύπους, επειδή μπορούν να χρησιμοποιήσουν δυναμική πληροφορία μαζί με την πληροφορία από τον πηγαίο κώδικα. Από την άλλη πλευρά, οι έλεγχοι στο χρόνο εκτέλεσης βεβαιώνουν μόνο συνθήκες που ικανοποιούν μια συγκεκριμένη εκτέλεση ενός προγράμματος και πρέπει να επαναλαμβάνονται για κάθε εκτέλεση.
Η ανάπτυξη λογισμικού σε γλώσσες με δυναμικούς τύπους συχνά υποστηρίζεται από πρακτικές όπως ο έλεγχος μονάδας (unit testing). Οι δοκιμές αποτελούν βασική πρακτική στην επαγγελματική ανάπτυξη λογισμικού και είναι ιδιαίτερα σημαντικές σε γλώσσες με δυναμικούς τύπους. Στην πράξη, οι δοκιμές μπορούν να εντοπίσουν ένα ευρύτερο πεδίο σφαλμάτων σε σχέση με το στατικό έλεγχο τύπων αλλά δεν είναι το ίδιο πλήρεις και εξαντλητικές.

Συνδυασμοί δυναμικών και στατικών τύπων

Η παρουσία στατικών τύπων σε μια γλώσσα προγραμματισμού δεν αποκλείει απαραίτητα τους μηχανισμούς των δυναμικών τύπων. Στη Java για παράδειγμα, καθώς και σε άλλες αντικειμενοστρεφείς γλώσσες, αν και χρησιμοποιούνται οι στατικοί τύποι, κάποιες λειτουργίες (downcasting) απαιτούν ελέγχους στο χρόνο εκτέλεσης, μια μορφή δυναμικών τύπων.
Κάποιες γλώσσες, όπως η Clojure, έχουν δυναμικούς τύπους αλλά επιτρέπουν τη χρήση ρητών σημειώσεων τύπων (explicit type hints) που έχουν ως αποτέλεσμα στατικούς τύπους. Ένας πιθανός λόγος που χρησιμοποιούνται είναι για τη βελτιστοποίηση κώδικα με απαιτήσεις ταχύτητας, με βάση τους στατικούς τύπους.
Από την έκδοση 4.0, το .NET Framework υποστηρίζει μια παραλλαγή των δυναμικών τύπων μέσω του χώρου ονομάτων System.Dynamic όπου ένα στατικό αντικείμενο (static) τύπου 'δυναμικό' ('dynamic') χρησιμοποιείται ως χώρος που το σύστημα χρόνου εκτέλεσης της πλατφόρμας .NET ρωτά για τις δυναμικές δυνατότητές του ώστε να επιλύσει μια αναφορά σε κάποιο αντικείμενο.

Στατικός και δυναμικός έλεγχος τύπων στην πράξη

Η απόφαση να χρησιμοποιηθούν στατικοί ή δυναμικοί τύποι εξαρτάται από πολλούς παράγοντες, με κάποιες αναγκαίες υποχωρήσεις σε κάθε περίπτωση (trade-offs).
Οι στατικοί τύποι μπορούν να εντοπίσουν σφάλματα τύπων στο χρόνο μεταγλώττισης, κάτι που κανονικά κάνει ένα πρόγραμμα πιο αξιόπιστο. Όμως, οι προγραμματιστές διαφωνούν σχετικά με το πόσο συχνά συμβαίνουν σφάλματα τύπων και ποιο ποσοστό από τα λάθη κατά την ανάπτυξη ενός προγράμματος εντοπίζεται με τους στατικούς τύπους. Οι υποστηρικτές των στατικών τύπων πιστεύουν ότι ένα πρόγραμμα είναι πιο αξιόπιστο όταν έχει περάσει τον έλεγχο τύπων ενώ οι υποστηρικτές των δυναμικών τύπων δείχνουν κατανεμημένο κώδικα που έχει αποδειχτεί αξιόπιστος. Η ισχύς των στατικών τύπων αυξάνεται όσο αυξάνεται και η ισχύς του συστήματος τύπων. Οι υποστηρικτές των γλωσσών με εξαρτώμενους τύπους (dependent types) όπως η Dependent ML και η Epigram, θεωρούν ότι σχεδόν όλα τα σφάλματα μπορούν να θεωρηθούν σφάλματα τύπων, αν οι τύποι που χρησιμοποιούνται σε ένα πρόγραμμα έχουν δηλωθεί σωστά από τον προγραμματιστή ή έχουν εξαχθεί σωστά από το μεταγλωττιστή.
Οι στατικοί τύποι συνήθως έχουν ως αποτέλεσμα μεταγλωττισμένο κώδικα που εκτελείται πιο γρήγορα. Όταν ο μεταγλωττιστής γνωρίζει ποιοι ακριβώς τύποι δεδομένων χρησιμοποιούνται, μπορεί να παράγει βελτιστοποιημένο κώδικα. Κάποιες γλώσσες με δυναμικούς τύπους όπως η Common Lisp επιτρέπουν προαιρετικά τη δήλωση τύπων για τη βελτιστοποίηση κατά αυτόν τον τρόπο. Οι στατικοί τύποι αποτελούν επομένως τη γενίκευση αυτού του φαινομένου σε όλο το εύρος του κώδικα. Βλ. βελτιστοποίηση (optimization).
Αντίθετα, οι δυναμικοί τύποι μπορούν να επιτρέψουν στο μεταγλωττιστή να εκτελείται πιο γρήγορα και στο διορθωτή να φορτώνει δυναμικά νέο κώδικα, επειδή οι αλλαγές στον πηγαίο κώδικα στις γλώσσες με δυναμικούς τύπους έχουν ως αποτέλεσμα λιγότερο περαιτέρω έλεγχο και λιγότερο κώδικα για επανεξέταση. Κάτι τέτοιο σε ορισμένες περιπτώσεις μπορεί να μικρύνει τον κύκλο διόρθωση-μεταγλώττιση-δοκιμή-αποσφαλμάτωση (edit-compile-test-debug).
Οι γλώσσες με στατικούς τύπους που δεν έχουν εξαγωγή τύπων (όπως η Java και η C) απαιτούν ο προγραμματιστής να δηλώνει όλους τους τύπους που πρόκειται να χρησιμοποιήσει μια μέθοδος ή μια συνάρτηση. Αυτό μπορεί να αποτελέσει επιπλέον τεκμηρίωση για το πρόγραμμα, την οποία ο μεταγλωττιστής δεν επιτρέπει στον προγραμματιστή να αγνοήσει ή να μην αντιστοιχεί στον κώδικα. Όμως μια γλώσσα μπορεί να έχει στατικούς τύπους χωρίς να απαιτεί δηλώσεις τύπων (π.χ. η Haskell, η Scala, και σε κάποιο βαθμό η C#), επομένως αυτό δεν είναι απαραίτητη συνέπεια των στατικών τύπων.
Οι δυναμικοί τύποι επιτρέπουν δομές που οι στατικοί τύποι απορρίπτουν ως μη επιτρεπτές. Για παράδειγμα, επιτρέπουν συναρτήσεις τύπου αποτίμησε (eval), που εκτελούν αυθαίρετα κομμάτια κώδικα. Επιπλέον οι δυναμικοί τύποι χειρίζονται καλύτερα κώδικα σε μεταβατική φάση (transitional code) και τη γρήγορη ανάπτυξη πρωτοτύπων (prototyping), όπως η χρήση μιας κενής δομής δεδομένων (ψευδο-αντικείμενο, mock object) στη θέση μιας πλήρους δομής δεδομένων (συνήθως για πειραματισμούς και δοκιμές).
Οι δυναμικοί τύποι χρησιμοποιούνται στο duck typing που μπορεί να υποστηρίζει ευκολότερη επαναχρησιμοποίηση κώδικα.
Οι δυναμικοί τύποι συνήθως διευκολύνουν το μεταπρογραμματισμό (metaprogramming). Για παράδειγμα, τα πρότυπα της C++ (templates) συνήθως είναι δυσκολότερα στη χρήση από αντίστοιχο κώδικα σε Ruby ή Python. Πιο προχωρημένες δομές χρόνου εκτέλεσης όπως οι μετακλάσεις και η ενδοσκόπηση (introspection) συχνά είναι πιο δύσχρηστες σε γλώσσες με στατικούς τύπους.

Εξειδικευμένα συστήματα τύπων

Έχουν δημιουργηθεί πολλά συστήματα τύπων για χρήση σε συγκεκριμένα περιβάλλοντα, με ειδικούς τύπους δεδομένων ή για περαιτέρω στατική ανάλυση προγράμματος. Συχνά βασίζονται σε ιδέες της τυπικής θεωρίας τύπων και εμφανίζονται σε ερευνητικά συστήματα.

Γραμμικοί τύποι

Οι γραμμικοί τύποι (linear types), που βασίζονται στη θεωρία της γραμμικής λογικής (linear logic), και σχετίζονται με τους τύπους μοναδικότητας (uniqueness types), είναι τύποι αποδίδονται σε τιμές που έχουν την ιδιότητα να υπάρχει ακριβώς μια αναφορά σε αυτές σε κάθε χρονική στιγμή. Είναι σημαντικοί για την περιγραφή μεγάλων τιμών που δεν αλλάζουν (immutable values) όπως οι συμβολοσειρές ή τα αρχεία, γιατί κάθε λειτουργία που ταυτόχρονα καταστρέφει ένα γραμμικό αντικείμενο και ταυτόχρονα δημιουργεί ένα παρόμοιο αντικείμενο (όπως ο κώδικας 'str = str + "a"') μπορεί να βελτιστοποιηθεί σαν επιτόπου τροποποίηση (in-place mutation). Κανονικά κάτι τέτοιο δε θα ήταν δυνατό, γιατί τέτοιες αλλαγές μπορεί να προκαλούσαν παρενέργειες σε μέρη του προγράμματος που έχουν αναφορές προς το αντικείμενο, κατά παράβαση της διαφάνειας αναφοράς (referential transparency). Χρησιμοποιούνται επίσης στο λειτουργικό σύστημα Singularity για επικοινωνία μεταξύ των διεργασιών, βεβαιώνοντας στατικά ότι οι διαδικασίες δε μπορούν να μοιράζονται αντικείμενα σε κοινή μνήμη, ώστε να αποφεύγονται καταστάσεις απροσδιοριστίας (race conditions). Η γλώσσα Clean (που είναι παρόμοια με τη Haskell) χρησιμοποιεί ένα σχετικό σύστημα τύπων ώστε να έχει καλύτερη ταχύτητα εκτέλεσης, μένοντας ασφαλής.

Τύποι τομής

Οι τύποι τομής (intersection types) περιγράφουν τιμές που ανήκουν ταυτόχρονα σε δύο άλλους τύπους, με επικαλυπτόμενα πεδία τιμών. Για παράδειγμα, στις περισσότερες υλοποιήσεις της C ο χαρακτήρας με πρόσημο (signed char) έχει εύρος μεταξύ -128 και 127 και ο χαρακτήρας χωρίς πρόσημο (unsigned char) έχει εύρος μεταξύ 0 και 255, επομένως ο τύπος τομής αυτών των δύο τύπων έχει εύρος μεταξύ 0 και 127. Ένας τέτοιος τύπος τομής θα μπορούσε να περαστεί με ασφάλεια σε συναρτήσεις που περιμένουν χαρακτήρες με πρόσημο ή χωρίς πρόσημο, γιατί και οι δύο τύποι είναι συμβατοί.
Οι τύποι τομής είναι χρήσιμοι για την περιγραφή υπερφορτωμένων (overloaded) τύπων συναρτήσεων: για παράδειγμα, αν ο τύπος των συναρτήσεων που δέχονται έναν ακέραιο και επιστρέφουν ακέραιο είναι "int → int", και ο τύπος των συναρτήσεων που δέχονται έναν αριθμό κινητής υποδιαστολής and επιστρέφουν αριθμό κινητής υποδιαστολής είναι "float → float", τότε η τομή αυτών των δύο τύπων μπορεί να χρησιμοποιηθεί για συναρτήσεις που κάνουν είτε το πρώτο είτε το δεύτερο, με βάση τον τύπο της παραμέτρου τους. Μια τέτοια συνάρτηση μπορεί να περαστεί σε κάποια άλλη συνάρτηση που περιμένει συνάρτηση τύπου "int → int" με ασφάλεια - απλά δε θα χρησιμοποιούσε τη λειτουργικότητα του τύπου "float → float".
Σε μια ιεραρχία με υποκλάσεις, η τομή ενός τύπου και ενός γονικού τύπου είναι ο τύπος χαμηλότερα στην ιεραρχία. Η τομή αδελφικών τύπων είναι ο κενός τύπος.
Η γλώσσα Forsythe περιλαμβάνει μια γενική υλοποίηση των τύπων τομής. Μια σχετική μορφή είναι οι refinement types.

Τύποι ένωσης

Οι τύποι ένωσης (union types) περιγράφουν τιμές που ανήκουν σε έναν μόνο από δύο τύπους. Για παράδειγμα, στη C, ο χαρακτήρας με πρόσημο έχει εύρος μεταξύ  -128 και 127, και ο χαρακτήρας χωρίς πρόσημο έχει εύρος μεταξύ 0 και 255, επομένως η ένωση αυτών των δύο τύπων έχει εύρος μεταξύ -128 και 255. Κάθε συνάρτηση που χειρίζεται αυτόν τον τύπο ένωσης θα έπρεπε να χειρίζεται τους ακεραίους σε όλο αυτό το εύρος. Γενικά, οι μόνες έγκυρες λειτουργίες σε έναν τύπο ένωσης είναι αυτές που είναι έγκυρες και στους δύο τύπους που ενώνονται. Η δομή "union" της C είναι παρόμοια με τους τύπους ένωσης, αλλά δεν έχει ασφαλή τύπο γιατί επιτρέπει λειτουργίες που είναι έγκυρες σε οποιονδήποτε από τους δύο τύπους, αντί και για τους δύο. Οι τύποι ένωσης είναι σημαντικοί στην ανάλυση προγράμματος, όπου χρησιμοποιούνται για την αναπαράσταση συμβολικών τιμών, των οποίων η ακριβής φύση (π.χ. τιμή, τύπος) δεν είναι γνωστή.
Σε μια ιεραρχία με υποκλάσεις, η ένωση ενός τύπου και ενός προγόνου του, είναι ο πρόγονος. Η ένωση δύο αδελφικών τύπων είναι υποτύπος του κοινού τους προγόνου (όλες οι λειτουργίες που επιτρέπονται στον κοινό πρόγονο επιτρέπονται και στον τύπο ένωσης, αλλά μπορεί επίσης να υπάρχουν και άλλες κοινές λειτουργίες).

Συμβατότητα: ισοδυναμία και υποτύποι

Ο ελεγκτής τύπων για μια γλώσσα με στατικούς τύπους πρέπει να βεβαιώνει ότι ο τύπος κάθε έκφρασης συμφωνεί με τον τύπο που περιμένει το περιβάλλον στο οποίο αυτή εμφανίζεται. Για παράδειγμα, σε μια εντολή ανάθεσης της μορφής x := e,
ο τύπος που συνάγεται για την έκφραση e πρέπει να συμφωνεί με τον τύπο που δηλώθηκε ή βρέθηκε αυτόματα για τη μεταβλητή x. Αυτή η συνέπεια ονομάζεται συμβατότητα (compatibility) και διαφέρει ανάμεσα στις γλώσσες προγραμματισμού.
Αν ο τύπος της e και ο τύπος της x είναι ο ίδιος και επιτρέπεται η ανάθεση για αυτόν, τότε πρόκειται για έγκυρη έκφραση. Σε απλά συστήματα τύπων, η ερώτηση αν δύο τύποι είναι συμβατοί ανάγεται στον αν είναι ίσοι ή ισοδύναμοι. Άλλες γλώσσες όμως έχουν άλλα κριτήρια για το πότε δύο εκφράσεις θεωρείται ότι σημαίνουν τιμές του ίδιου τύπου. Αυτές οι θεωρίες ισοδυναμίας (equational theories) γενικά διαφέρουν, με δύο ακραίες περιπτώσεις να είναι τα δομικά συστήματα τύπων (structural type systems), στα οποία δύο τύποι είναι ισοδύναμοι όταν περιγράφουν τιμές με την ίδια δομή, και τα ονομαστικά συστήματα τύπων (nominative type systems), στα οποία δύο συντακτικά ξεχωριστοί τύποι ποτέ δε σημαίνουν τον ίδιο τύπο (δηλαδή δύο τύποι πρέπει να έχουν το ίδιο "όνομα" για να είναι ίσοι).
Σε γλώσσες με υποτύπους (subtyping), η σχέση συμβατότητας είναι πιο πολύπλοκη. Ειδικότερα, αν ο A είναι υποτύπος του B, τότε μια τιμή τύπου A μπορεί να χρησιμοποιηθεί σε ένα περιβάλλον που απαιτείται μια τιμή τύπου B, ακόμα και αν το ανάποδο δεν ισχύει. Όπως στην ισοδυναμία, η σχέση υποτύπου ορίζεται διαφορετικά για κάθε γλώσσα προγραμματισμού, με πολλές δυνατές παραλλαγές. Η παρουσία παραμετρικού και περιστασιακού (ad hoc) πολυμορφισμού σε μια γλώσσα μπορεί επίσης να έχει επιπτώσεις στη συμβατότητα τύπων.

Προγραμματιστικό στυλ

Κάποιοι προγραμματιστές προτιμούν τις γλώσσες με στατικούς τύπους ενώ άλλοι αυτές με δυναμικούς τύπους. Οι γλώσσες με στατικούς τύπους ειδοποιούν τον προγραμματιστή για σφάλματα τύπων κατά τη μεταγλώττιση ενώ μπορεί να είναι και πιο γρήγορες στο χρόνο εκτέλεσης. Οι υποστηρικτές των γλωσσών με δυναμικούς τύπους υποστηρίζουν ότι είναι καλύτερες στη γρήγορη ανάπτυξη πρωτότυπου λογισμικού (rapid prototyping) και ότι τα σφάλματα τύπων είναι μόνο ένα υποσύνολο των σφαλμάτων ενός προγράμματος. Επίσης, συνήθως δεν είναι αναγκαίο να ορίζονται ρητά όλοι οι τύποι σε μια γλώσσα που υποστηρίζει εξαγωγή τύπων ενώ πολλές γλώσσες με δυναμικούς τύπους έχουν βελτιστοποιήσεις στο χρόνο εκτέλεσης που μπορούν να παράγουν γρήγορο κώδικα με ταχύτητα που να πλησιάζει αυτήν των μεταγλωττιστών για στατικές γλώσσες, συχνά χρησιμοποιώντας μερική εξαγωγή τύπων (partial type inference).