Oh man, ich bin aufgeregt zu versuchen, diese Frage so gut wie möglich zu beantworten. Ich hoffe ich kann meine Gedanken richtig in Ordnung bringen.
Wie @Doval und der Fragesteller bereits erwähnt haben (wenn auch grob), verfügen Sie nicht wirklich über ein Typensystem. Sie haben ein System dynamischer Prüfungen mit Tags, das im Allgemeinen viel schwächer und auch viel weniger interessant ist.
Die Frage "Was ist ein Typensystem?" Kann durchaus philosophisch sein, und wir könnten ein Buch mit unterschiedlichen Standpunkten zu diesem Thema füllen. Da dies jedoch eine Website für Programmierer ist, werde ich versuchen, meine Antwort so praktisch wie möglich zu halten (und Typen sind wirklich äußerst praktisch in der Programmierung, trotz der Meinung einiger).
Überblick
Beginnen wir damit, dass wir verstehen, wozu ein Typensystem gut ist, bevor wir uns mit den formaleren Grundlagen befassen. Ein Typensystem gibt unseren Programmen Struktur . Sie sagen uns, wie wir verschiedene Funktionen und Ausdrücke miteinander verbinden können. Ohne Struktur sind Programme unhaltbar und äußerst komplex und können beim kleinsten Fehler des Programmierers Schaden anrichten.
Das Schreiben von Programmen mit einem Typensystem ist wie das Fahren einer Pflege in neuwertigem Zustand - die Bremsen funktionieren, die Türen schließen sicher, der Motor ist geölt usw. Das Schreiben von Programmen ohne Typensystem ist wie das Fahren eines Motorrads ohne Helm und mit Rädern aus Spaghetti. Sie haben absolut keine Kontrolle über Ihre.
Um die Diskussion zu Boden, lassen Sie uns sagen , dass wir eine Sprache mit wörtlichen Ausdruck haben num[n]
und str[s]
die repräsentieren die Zahl n und die Zeichenfolge s, bzw., und primitive Funktionen plus
und concat
mit der Bedeutung bestimmt. Natürlich möchten Sie nicht in der Lage sein, etwas wie plus "hello" "world"
oder zu schreiben concat 2 4
. Aber wie können wir das verhindern? Von vornherein gibt es keine Methode, um die Zahl 2 von der Zeichenfolgen-Literal-Welt zu unterscheiden. Wir möchten sagen, dass diese Ausdrücke in verschiedenen Zusammenhängen verwendet werden sollten. Sie haben verschiedene Typen.
Sprachen und Typen
Lassen Sie uns einen Schritt zurücktreten: Was ist eine Programmiersprache? Im Allgemeinen können wir eine Programmiersprache in zwei Ebenen unterteilen: die Syntax und die Semantik. Diese werden auch als Statik bzw. Dynamik bezeichnet. Es zeigt sich, dass das Typensystem notwendig ist, um die Wechselwirkung zwischen diesen beiden Teilen zu vermitteln.
Syntax
Ein Programm ist ein Baum. Lassen Sie sich nicht von den Textzeilen täuschen, die Sie auf einem Computer schreiben. Dies sind nur die lesbaren Darstellungen eines Programms. Das Programm selbst ist ein abstrakter Syntaxbaum . Zum Beispiel könnten wir in C schreiben:
int square(int x) {
return x * x;
}
Das ist die konkrete Syntax für das Programm (Fragment). Die Baumdarstellung ist:
function square
/ | \
int int x return
|
times
/ \
x x
Eine Programmiersprache stellt eine Grammatik bereit , die die gültigen Bäume dieser Sprache definiert (entweder konkrete oder abstrakte Syntax kann verwendet werden). Dies geschieht normalerweise mit einer BNF-Notation. Ich würde annehmen, dass Sie dies für die Sprache getan haben, die Sie erstellt haben.
Semantik
OK, wir wissen, was ein Programm ist, aber es ist nur eine statische Baumstruktur. Vermutlich möchten wir, dass unser Programm tatsächlich etwas berechnet . Wir brauchen Semantik.
Die Semantik der Programmiersprachen ist ein reiches Studiengebiet. Grundsätzlich gibt es zwei Ansätze: die Denotationssemantik und die Operationssemantik . Die Denotationssemantik beschreibt ein Programm, indem sie es in eine zugrunde liegende mathematische Struktur abbildet (z. B. die natürlichen Zahlen, stetigen Funktionen usw.). Das gibt unserem Programm einen Sinn. Die operative Semantik hingegen definiert ein Programm, indem sie dessen Ausführung detailliert beschreibt. Meiner Meinung nach ist die operationale Semantik für Programmierer (einschließlich meiner selbst) intuitiver. Bleiben wir also dabei.
Ich werde nicht näher auf die Definition einer formalen operativen Semantik eingehen (die Details sind ein bisschen kompliziert), aber im Grunde wollen wir Regeln wie die folgenden:
num[n]
ist ein Wert
str[s]
ist ein Wert
- Wenn
num[n1]
und num[n2]
zu den ganzen Zahlen n_1$ and $n_2$, then
plus (num [n1], num [n2]) `ausgewertet wird, ergibt sich die ganze Zahl $ n_1 + n_2 $.
- Wenn
str[s1]
und str[s2]
zu den Zeichenfolgen s1 und s2 concat(str[s1], str[s2])
ausgewertet wird, wird zu der Zeichenfolge s1s2 ausgewertet.
Die Regeln sind in der Praxis viel formeller, aber Sie verstehen das Wesentliche. Wir stoßen jedoch bald auf ein Problem. Was passiert, wenn wir folgendes schreiben:
concat(num[5], str[hello])
Hm. Das ist ein ziemliches Rätsel. Wir haben nirgendwo eine Regel definiert, wie eine Zahl mit einer Zeichenfolge verkettet werden soll. Wir könnten versuchen, eine solche Regel zu erstellen, aber wir wissen intuitiv, dass diese Operation bedeutungslos ist. Wir wollen nicht, dass dieses Programm gültig ist. Und so werden wir unaufhaltsam zu Typen geführt.
Typen
Ein Programm ist ein Baum, wie er durch die Grammatik einer Sprache definiert ist. Programmen wird durch Ausführungsregeln eine Bedeutung gegeben. Einige Programme können jedoch nicht ausgeführt werden. Das heißt, einige Programme sind bedeutungslos . Diese Programme sind falsch geschrieben. Tippen kennzeichnet also sinnvolle Programme in einer Sprache. Wenn ein Programm gut typisiert ist, können wir es ausführen.
Nennen wir einige Beispiele. Wie bei den Bewertungsregeln werde ich auch hier die Schreibregeln informell präsentieren, aber sie können rigoros gestaltet werden. Hier sind einige Regeln:
- Ein Token des Formulars
num[n]
hat einen Typ nat
.
- Ein Token des Formulars
str[s]
hat einen Typ str
.
- Wenn expression
e1
den Typ nat
und expression e2
den Typ hat nat
, hat der Ausdruck plus(e1, e2)
den Typ nat
.
- Wenn expression
e1
den Typ str
und expression e2
den Typ hat str
, hat expression concat(e1, e2)
den Typ str
.
Nach diesen Regeln gibt es plus(num[5], num[2])
also einen Typ nat
, dem wir jedoch keinen Typ zuweisen können plus(num[5], str["hello"])
. Wir sagen, dass ein Programm (oder Ausdruck) gut typisiert ist, wenn wir ihm einen beliebigen Typ zuweisen können, und dass es ansonsten schlecht typisiert ist. Ein Typensystem ist gesund, wenn alle gut getippten Programme ausgeführt werden können. Haskell ist gesund; C ist nicht.
Fazit
Es gibt andere Ansichten zu Typen. Typen entsprechen in gewisser Weise der intuitionistischen Logik und können in der Kategorietheorie auch als Objekte angesehen werden. Das Verständnis dieser Zusammenhänge ist faszinierend, aber nicht unbedingt erforderlich, wenn man lediglich eine Programmiersprache schreiben oder sogar entwerfen möchte. Das Verstehen von Typen als Werkzeug zur Steuerung von Programmformationen ist jedoch für das Entwerfen und Entwickeln von Programmiersprachen von wesentlicher Bedeutung. Ich habe nur die Oberfläche zerkratzt, was Typen ausdrücken können. Ich hoffe, Sie denken, dass sie es wert sind, in Ihre Sprache aufgenommen zu werden.