 Herzlich willkommen zu Programmierparadigmen. In diesem Teil der Veranstaltung geht es um Typsysteme und Typen in Programmiersprachen. Quasi jede Programmiersprache hat ja irgendeine Form von Typsystem und irgendeine Art Werte, in Typen zu abstrahieren. Und was wir in diesem Teil der Veranstaltung machen werden, ist uns anzuschauen, warum man Typen überhaupt braucht und was diese Typen-Systeme denn genau machen. Außerdem werden wir natürlich wieder verschiedene Sprachen anschauen und schauen, wie Typen in diesen Sprachen tatsächlich umgesetzt werden. Als kleine Aufwärmübung und um vielleicht erst mal zu sehen, welche Rolle Typen in Programmiersprachen denn überhaupt zu spielen können, habe ich hier ein kleines Quiz, wo wir einfach mal vier Ausdrücke in JavaScript sehen und die Frage ist, zu welchen Werten diese Ausdrücke denn evaluiert werden, wenn wir die so in JavaScript hinschreiben würden. Wie immer bei den Quizzes würde ich Sie bitten, das Video hier kurz anzuhalten, dann selbstständig darüber nachzudenken, was denn das Ergebnis ist, dann den Ilias abzustimmen, um zu sehen, ob das, was Sie abstimmen, auch so ungefähr das ist, was die anderen Studierenden denken. Und anschließend geht es dann weiter und ich erkläre die Lösung. Schauen wir uns mal die Lösung an. Also wir haben diese vier Ausdrücke, der erste evaluiert zu false und die Lösung hier ist oder der Grund ist ganz einfach, dass wir natürlich zwei Strings haben. Diese Strings aber nicht dasselbe sind und deswegen sind die nicht gleich und das Ergebnis dieses Ausdrucks ist false. Die nächsten beiden Ausdrücke sind in dem Sinne interessant, dass hier Werte von verschiedenen Typen miteinander verglichen werden und genauer gesagt jeweils ein Wert vom Typ Number und ein Wert vom Typ String. Was die Sprache hier macht, ist, dass der String implizit konvertiert wird in eine Number, das sind man coercion, also das ist eine implizierte Typkonvertierung und in diesem Fall werden diese beiden Strings, also sowohl der leere String als auch der String, der diese Null enthält, jeweils in die Zahl Null konvertiert und das wird dann verglichen mit dem Wert, den wir auf der linken Seite haben, was ja auch die Zahl Null ist und da Null gleich Null ist, evaluieren beide diese Ausdrücke zu true. Der vierte Ausdruck, den wir hier haben, evaluiert zu false und der Grund ist folgendes. Hier haben wir auf der linken Seite ein Boolean Wert und auf der rechten Seite irgendein anderen Typ, in dem Fall ein String und die Regeln, die die Sprache JavaScript für diese Art Vergleich aufstellt, ist folgendes. Wenn wir ein Boolean und irgendein anderen Typen haben, dann wird zunächst der Boolean in eine Zahl konvertiert, in dem Fall wird das False also zu Null konvertiert. Der andere Typ oder der Wert des anderen Typ ist auf der rechten Seite, in dem Fall ein String, wird auch in eine Zahl konvertiert, da dieser String hier false ist, was nicht so richtig in der Zahl konvertierbar ist, ist der Wert den wir hier kriegen, not a number, was ein legaler Wert des Types number ist, auch wenn der Name not a number vielleicht nicht so klingt. Und dann werden diese beiden Zahlen, nämlich Null und not a number, miteinander verglichen und dann diese Zahlen dann unterschiedlich sind, ist schlussendlich der Wert des Ausdrucks false. Was diese kleine Einführungen mithilfe des Quizzes zeigen soll, ist, dass Typen in Programmiersprachen manchmal gar nicht so einfach sind und es ist wichtig ist, A zu verstehen, welchen Typ hat denn eigentlich ein Wert und B, welche Regeln hat denn eigentlich meine Programmiersprache, um mit diesen Typen umzugehen, also um zum Beispiel zu überprüfen, ob eine Ausdruck überhaupt Typkurekt ist oder um diesen Ausdruck dann schlussendlich zu evaluieren. Diese Teil der Veranstaltung wird sechs Teile haben und das erste ist einfach eine kleine Einführung in Typen und Typsysteme, wo es darum geht, was sind Typen überhaupt und wofür brauchen wir die in Programmiersprachen. Fangen wir damit mal direkt an und zwar mit der Frage, was ist eigentlich so ein Typ, also was sind diese Typen? In fast allen Programmiersprachen haben zwei Dinge Typen, nämlich zum einen Ausdrücke und zum anderen Speicherobjekte und was diese Typen im Prinzip einfach machen ist eine Abstraktion zu beschreiben, die sagt, dass eine Menge von Ausdrücken oder eine Menge von Speicherobjekten bzw. die Werte, die darin gespeichert sind, Teil eines gemeinsamen Dinge sind und dieses Ding nennt man einfach Typ. Hier sind mal ein paar Beispiele, die gar nicht in einer bestimmten Sprache unbedingt sind, aber ich denke, sie werden gleich trotzdem verstehen, was damit gemeint ist. Zum einen haben wir hier so ein Assignment, wo wir sagen x ist gleich 4 und was dieses Assignment implizit ausdrückt, ist, dass diese Variable x den Typ number oder vielleicht auch integer je nach Sprache hat, denn das ist der Wert, den wir diese Variable zuordnen. Ein anderes Beispiel ist eine Deklaration von einer Variable, wo wir zum Beispiel explizit sagen, dass eine Variable n den Typ int hat und damit den Typ explizit angeben und sozusagen der Programmiersprache Bescheid geben, dass das der Typ ist, den diese Variable immer haben muss. Wenn ich einen Ausdruck habe, wie zum Beispiel a plus b, dann hat dieser Ausdruckt insgesamt natürlich auch ein Typ und in vielen Sprachen hängt dieser Typ von den Typen der Operanten ab, die in diesem Ausdruck verwendet werden. Also in dem Fall hängt der Typ von a plus b davon ab, welchen Typ denn überhaupt a und welchen Typ den überhaupt b hat. Und schlussendlich noch ein Beispiel von einem anderen Ausdruck, nämlich dieses new x, was zum Beispiel der Aufruf eines Konstruktors in Java sein könnte. Dieser Ausdruck hat natürlich auch ein Typ und würde dann zum Beispiel den Typ x haben, wenn x eine bestimmte Klasse ist und damit natürlich auch ein Typ definiert. Jetzt ist die Frage, warum wollen wir diese Typen überhaupt haben? Also was ist der Vorteil davon, in Programmiersprachen Typen zu definieren und quasi jedem Wert, jedem Ausdruck und jedem Speicherobjekt einen bestimmten Typen zuzuordnen. Ein Grund ist, dass Typen Kontext für die Evolution von bestimmten Operationen geben. Also die Sagen im Prinzip erst, was diese Operation tatsächlich bedeutet. Zum Beispiel hatten wir gerade eben diesen Ausdruck a plus b gesehen und die Bedeutung dieses Ausdruckes hängt in vielen Sprachen tatsächlich erst mal davon ab, welche Typen a und b eigentlich haben. Wenn a und b Zahlen sind, dann bedeutet plus in der Regel Addition. Wenn a und b aber Strength sind, dann bedeutet plus in vielen Sprachen Konkatenieren von diesen Strengths, also was ganz anderes. Das heißt, ohne die Typen zu wissen, kann die Implementierung der Programmiersprache eigentlich gar nicht wissen, was genau mit diesem Code gemeint ist. Aber wenn wir die Typen haben, also diese Kontextinformation haben, dann hat dieser Operation tatsächlich eine Bedeutung. Ein anderes Beispiel ist dieses dieser Konstrukteaufruf mit einer etwas anderen Syntax als gerade eben, wo wir New X sagen und je nach dem welcher Typ jetzt hinter diesem X steht, wird andere Code aufgerufen, der tatsächlich die Initialisierung dieses neu erstellten Objektes vornimmt und ohne den Typen von diesem X zu kennen, wüsste die Programmiersprachen Implementierung natürlich wieder nicht, welche Initialisierung Code hier tatsächlich benutzt werden soll. Also diese Kontext Informationen, die Typen angeben, ist sehr, sehr wichtig für die Implementierung der Programmiersprache, denn ohne diese Kontextinformation wäre die Bedeutung von vielen Code gar nicht wirklich klar. Ein zweiter Grund, warum Typen in Programmiersprachen sehr wichtig sind, ist, dass Typen einschränken, welche Operationen überhaupt Valide sind und welche Arten von Programmen man überhaupt hinschreiben soll, da sie tatsächlich auch Sinn machen. Ein Beispiel, wo syntaktisch valide Operationen semantisch aber doch keinen Sinn machen ist, zum Beispiel, wenn ich zwei Werte, die verschiedene Typen haben miteinander verbinde, zum Beispiel durch Additionen. Also wenn ich zum Beispiel einen Character und einen Record miteinander versuche zu addieren und die durch Plus verbinde, dann kann das syntaktisch durchaus korrekt sein in der Sprache, macht aber semantisch eigentlich keinen Sinn und es ist zumindest in den meisten Sprachen auch nicht definiert, was das überhaupt bedeuten würde und mit Hilfe von Typen kann man solchen nicht sinnvollen Code ganz gut erkennen. Ein anderes Beispiel ist, wenn ich zum Beispiel eine Funktion habe, die ein Logarithmus normalerweise einer Zahl berechnen würde und gebe jetzt aber eine Menge in diese Funktion rein, möchte also sozusagen den Logarithmus eine Menge berechnen, dann macht das eigentlich keinen Sinn und mit Hilfe von Typen könnte man sowas ganz einfach finden. Also was Typen an dieser Stelle machen, ist den Entwicklern zu helfen, bestimmte Arten von Fehlern früh zu finden und somit diese Fehler nicht erst, zum Beispiel, wenn das Programm getestet wird oder wenn es dann tatsächlich in der Produktion verwendet wird, zu finden, sondern eben schon beim Schreiben, sofern zumindest die Sprache diese Typüberprüfung schon beim Schreiben des Programms vornimmt. Ein dritter Grund, warum Typen sehr wichtig sind in Programmiersprachen, ist, dass sie die Lesbarkeit und Verständlichkeit von Source Code deutlich erhöhen können. Im Prinzip sind Typen so eine Art formal definierte Dokumentation. Das heißt, ich schreibe im Prinzip zusätzliche Informationen über meine variablen Funktionen und so weiter in den Quelltext und diese zusätzliche Information bietet eine Art Dokumentation für entweder mich selbst, wenn ich den Code später verstehen muss oder natürlich auch für andere Programmierer, die sich meinen Programm anschauen wollen. Als Ergebnis ist der Code einfacher zu warten und auch einfacher zu erweitern, weil man eben nicht raten muss, welchen Typ zum Beispiel eine Funktion hat, sondern das tatsächlich formal dokumentiert hat und diese Dokumentation durch das Typsystem auch in einem einheitlichen Format ist und man gleich sieht, aha, der Return Value dieser Funktion ist zum Beispiel ein Int. Manchmal macht Typen den Code auch etwas komplexer und damit schwerer zum Schreiben. Also da gibt es immer so ein Trade-off zwischen der Schwierigkeit, den Code einmal hinzuschreiben und den Schwierigkeiten, die man dann vielleicht hat, wenn der Code geschrieben wurde und zum Beispiel keine Typannotation hat. Also da muss man immer ein bisschen aufpassen, dass man schlussendlich eine gute Balance findet und wir werden sehen, dass manche Sprachen da besonders hilfreich sind. Schlussendlich gibt es noch einen vierten Grund, warum Typen in Programmiersprachen sehr nützlich sind und das ist, um bestimmte Optimierung zu ermöglichen, die der Compiler vornehmen kann. Als kleines Beispiel, wenn der Compiler zum Beispiel die Typen von zwei Werten weiß, dann kann er vielleicht inferieren, dass bestimmtes Verhalten nicht möglich ist und dieses Wissen dann nutzen, um bestimmte Optimierung durchzuführen. Wenn ich zum Beispiel ein Assignment habe, wo ein Typ 1 an eine bestimmte Variable geschrieben wird, dann weiß ich ganz sicher, dass dieses Assignment keine Werte vom Typ 2 beeinflussen wird, zumindest sofern Typ 1 und Typ 2 nicht kompatibel miteinander sind und dieses Wissen, das bestimmtes Verhalten eben nicht auftreten kann, hilft bestimmte Optimierung durchzuführen. Das Ganze funktioniert interessanterweise, sowohl wenn diese Typen explizit spezifiziert sind, also wenn ich im Source Code wirklich hinschreibe, was die Typen meiner Ausdrücke und Speicherobjekte und so weiter sind, als auch wenn die Typen implizit inferiert werden, also selbst wenn der Compiler diese Typen nur weiß, weil er sie inferiert und wir werden später in der Veranstaltung noch sehen, wie genau das funktioniert. Auch dann kann dieses Wissen benutzt werden, um Optimierung im Compiler auszuführen. So, jetzt wissen Sie, warum Typen nützlich sind. Die traurige Wahrheit ist aber, dass das, was tatsächlich auf dem Computer ausgeführt wird, eigentlich gar keine Typen hat. Also die Bits, die schlussendlich auf dem Hardware Level oder auch auf dem Assembler Level hin und her geschoben und verarbeitet werden, haben in der Regel keine Typen. Also auf fast alle Hardware werden die Werte nicht typisiert, sondern sind einfach nur Ruhe, Bits. Das heißt, eine Reihe von Bits kann auch ganz verschiedene Sachen eigentlich beinhalten. Das kann entweder Source Code sein, das kann vielleicht ein Integer sein oder es kann eine Adresse auf ein anderes Stück im Speicher sein und all das ist nicht wirklich codiert oder typisiert, sondern man sieht eigentlich erst mal auf dem Hardware Level nur die Bits. Genauso ist es mit den meisten Assembler Sprachen auch dies sind nicht typisiert. Das heißt, im Prinzip kann jede Operation, die wir in so einer Assembler Sprache haben, auf beliebige Werte, die an beliebigen Stellen im Speicher geschrieben wurden, angewandt werden und das Typsystem existiert an der Stelle eigentlich nicht oder existiert praktisch nicht, sondern auf dem Assembler Level kann in der Regel jede mögliche Operation ausgeführt werden. Zum Glück programmieren wir aber nicht indem wir direkt die Hardware manipulieren oder indem wir Assembler Code schreiben, zumindest in den meisten Fällen wird es heutzutage nicht mehr gemacht, sondern wir benutzen Programmiersprachen. Und in einer Programmiersprache gibt es in der Regel ein Typsystem und dieses Typsystem definiert dann die Typen und assoziiert diese Typen mit bestimmten Konstrukten in unserer Programmiersprache. Quasi jeder Sprache hat eigentlich jedes Konstrukt entweder ein Typ oder bezieht sich in irgendeiner Form auf einen Typ. Also ein paar Beispiele sind zum Beispiel Konstanten, die vielleicht einen Namen haben, die haben in der Regel ein Typ, der beschreibt, welchen, welche Art Wert hinter dieser Konstante steht. So ähnlich für Variablen, bloß dass der Wert sich dann natürlich im Laufe des Programms ändern kann, aber trotzdem ist in vielen Sprachen das so, dass diese Variable einen bestimmten Typ hat. Das heißt, was auch immer ich in dieser Variable speichere, es muss immer von diesen bestimmten Typen sein. Das Gleiche gilt natürlich zum Beispiel auch für Felder eines Records oder für Functions, die bestimmte Argumente von bestimmten Typen erwarten und anschließend dann bestimmte Werte, die Return-Values, die auch wieder von bestimmten Typen sein müssen zurückgehen. Je nach Sprache sind diese Regeln strenger oder weniger streng und wir werden da verschiedene Varianten kennenlernen, aber in quasi jeder Sprache gibt es irgendeine Form von Typsystem, die irgendeine Form von Regeln aufstellt. Was sind das für Regeln? Es gibt im Prinzip 3 Arten von Regeln, die hier relevant sind. Zum einen gibt es in jeder Sprache Regeln darüber, welche Typen erquivalent sind, die also beschreiben, welche Werte ich direkt vergleichen kann oder vielleicht zueinander zuweisen kann, sodass das Ganze dann typkurekt ist. Eine andere Art von Regel ist die Compatibility, also die Kompatibilität von Typen, die uns sagt, ob zum Beispiel zwei Variablen, die verschiedene Typen haben, trotzdem miteinander kompatibel sind, also dass ich zum Beispiel die eine Variable der anderen zuweisen kann und das trotzdem noch typkurekt ist. Und Schlussendlich gibt es in vielen Sprachen auch noch Typ-Inferenz, also wenn zum Beispiel nicht alle Typen explizit vom Programmierer angegeben werden müssen, dann kann der Compiler oder die Laufzeitumgebung oft Typen inferieren, also weiß quasi das bestimmte Ausdrücke oder Variablen ein Typen haben, ohne dass das wirklich explizit angegeben wurde. Wenn ich jetzt so ein Typsystem habe, dann nutzt das unter anderem die Sprachimplementierung für das Type Checking, also für das Überprüfen, ob das Programm tatsächlich den Typregeln oder insbesondere den Typkompatibilitätsregeln entspricht. Wenn das nicht der Fall ist, dann gibt es entweder, bevor das Programm ausgeführt wird oder vielleicht auch während das Programm ausgeführt wird, dann einen Fehler. Und hier ist mein kleines Beispiel von einem Java-Programm, wo ich so eine Integer-Variable definiere und die dann verwende, um in diesen String namens B was reinzuschreiben. Und wenn ich das Ganze jetzt versuche zu kompilieren, dann würde ich vom Java-Compiler einen Typfehler kriegen, der mir einfach sagt, dass dieser Ausdruck hier drüben dieses A-2 ein Integer ergibt, denn ich nehme diesen Integer, der N-A steht und zieht davon zwei ab, dass ich das aber dann leider nicht in diese Variable B schreiben kann, weil B vom Typ String ist und das nicht den Typ-Kompatibilitätsregeln der Java-Sprache entspricht. So, das soll es auch schon gewesen sein für diesen ersten Teil dieses Moduls zum Thema Typ-Systeme. Das war also eine kleine Einführung. Ich hoffe, Sie haben jetzt so eine ungefähre Idee davon, warum man überhaupt Typen in Programmiersprachen hat und was diese Typen denn eigentlich so alles bedeuten und wobei diese Typen einem auch helfen können. Und in den nächsten Teilen des Moduls werden wir uns das Ganze dann noch ein bisschen genauer anschauen. Danke erst mal fürs Zuhören und bis zum nächsten Mal.