 Ja, wir kommen zu Programmierparadigmen und zu diesem sechsten Teil im Modul TypSysteme, in dem es um die formale Definition von TypSystemen geht und wie man das Ganze konkret aufschreibt, wenn man so ein TypSystem designt oder entwickelt. Die TypSysteme, über die wir bis jetzt gesprochen haben, waren im Prinzip die TypSysteme, wie sie so in einem Compiler implementiert sind. Aber neben dieser Implementierung in Compiler oder vielleicht auch Laufzeitumgebung von Sprachen, werden TypSysteme oft formal aufgeschrieben und formal definiert und viele von denen, oder zumindest manche von denen, die formal aufgeschrieben werden, werden schlussendlich auch mal implementiert. Dieser TypSystem, das dieses Feld, was sich mit TypSystemen beschäftigt, ist ein aktives Forschungsfeld und es gibt eigentlich jedes Jahr eine ganze Reihe von Publikationen dazu, die sich insbesondere auf neue Sprachen konzentrieren, die vielleicht erst vor kurzem populär geworden sind oder auch darauf konzentrieren, besonders starke Typgarantien für vielleicht existierende Sprachen zu geben, in denen das aktuell genutze TypSystem nicht so ausdrucksstark oder Garantie behaftet ist, wie man sich das vielleicht wünscht. Was wir jetzt hier in diesem Teil der Vorlesung machen wollen, ist uns für eine ganz, ganz kleine Spielsprache einfach mal so ein formal definiertes TypSystem anzuschauen und zwar für sogenannte typed expressions, also für ganz einfache Ausdrücke, die Typen haben und die Frage ist, wie kann man aufschreiben, welche Typen diese Ausdrücke denn haben und wie kann man aufschreiben, ob bestimmte Typen, ob bestimmte Ausdrücke denn überhaupt so legal aufgeschrieben werden können. Bevor wir das TypSystem für diese Ausdrücke definieren können, müssen wir vielleicht erst mal aufschreiben, was diese Ausdrücke überhaupt sind und das machen wir, indem wir die Sündtags dieser kleinen Spielsprache, die wir hier anschauen, einmal aufschreiben. Also unsere Ausdrücke schreiben wir alle in dieses Nicht-Terminal-T und die können folgendes sein, also wir haben zum einen den Ausdruck True, der einfach den Boolean Boolean Wert True angibt und den dazu passenden anderen, nämlich False. Dann können wir Ausdrücke aufschreiben, indem wir sagen, wenn ein bestimmter Ausdruck T gilt, dann gibt es einen anderen Ausdruck, den wir sehen wollen und falls nicht, dann gibt es noch einen anderen Ausdruck, das heißt dieses if-then-else gibt es sozusagen einen Wert zurück, der entweder das zweite T hier ist, wenn das erste T zu True evaluiert oder eben das dritte T hier ist, wenn der erste Ausdruck zu False evaluiert. Was ich auch machen kann, neben diesen Ausdrücken, die in der Regel Boolean oder eben einen Boolean in der Bedingung des if brauchen, ist, dass ich den Wert 0 als Ausdruck habe, das heißt wir können auch Zahlen in unseren Ausdrücken haben. Dann haben wir diesen Successor-Operator, der einen bestimmten Ausdruck T nimmt und einfach den nächsten Wert zurückgibt, also zum Beispiel Successor von 3 wäre 4 und equivalent dazu habe ich auch diesen Predecessor-Ausdruck, der den Vorgänger eines bestimmten Wertes zurückgibt, also zum Beispiel der Predecessor von 4 wäre dann 3. Und um von Zahlen dann auch wieder zu den Wulchenwerten zu kommen, habe ich diesen is0-Operator, der für eine gegebene Zahl zurückgibt, ob diese Zahl tatsächlich 0 ist oder eben nicht. Was ich jetzt hier gemacht habe, ist die Sündtagsaufzuschreiben, was ich an der Stelle nicht mache, weil das über die Vorlesung doch ein bisschen hinausgeht, ist die Semantik dieser kleinen Sprache noch formal aufzuschreiben. Wer sich dafür interessiert, kann dann gerne mal in unserer Master Level-Veranstaltung wieder schauen, wo wir teilweise dann sowas machen. Aber hier machen wir das nicht. Was ich aber schon dessen dann hier machen möchte, ist einfach nochmal 2-3 Beispiele dafür angeben, also Beispiele von Ausdrücken, die wir in dieser kleinen Spielsprache denn aufschreiben könnten. Also ein Ausdruck, der hier aufgeschrieben werden könnte, wäre Successor von 0 und der evaluiert dann schlussendlich, wie man sich vielleicht vorstellen kann, zu 1 oder mal etwas komplexerer. Ich könnte ein Ausdruck hinschreiben, der schaut, ob es 0 gilt, und zwar für den predecessor des successors von 0. Und wenn das der Fall ist, dann soll dieser Ausdruck 0 zurückgeben und ansonsten sollte dieser Ausdruck den Successor von 0 zurückgeben. Also 1. Und weil der predecessor des successors von 0 eben wieder 0 ist, evaluiert diese Condition, diese Bedingung zu True. Das heißt, wir geben das zurück, was hier in dem Wenn drin steht. Das heißt, dieser komplette Ausdruck würde dann zu 0 evaluieren. So, jetzt haben wir die Sprache definiert. Und wenn man sich einfach nur die Syntax anschaut, dann sieht man, dass es da eine ganze Reihe von möglichen Ausdrücken gibt, die eigentlich gar keinen Sinn machen. Also ich kann da Ausdrücke hinschreiben, die so taktisch legal sind, weil die Grammatik das erlaubt, aber diese Ausdrücke können eigentlich gar nicht evaluiert werden. Man wüsste gar nicht, was das denn eigentlich bedeutet. Und wenn wir jetzt einfach all diese möglichen Ausdrücke zur Implementierung dieser kleinen Spielsprache geben würden, dann würde diese Implementierung dann irgendeiner Stelle stecken bleiben oder einfach nicht weiter wissen oder vielleicht einfach einen Fehler zurückgeben, weil eben diese Ausdrücke nicht evaluiert werden können. Schauen wir uns mal Beispiele an von Ausdrücken, von denen manche tatsächlich Sinn machen und evaluiert werden können, aber andere eben nicht. Also wenn ich zum Beispiel Folgendes hinschreiben würde, if und dann is 0 von 0, then true als 0, dann wäre das ein Ausdruck, der so keinen Sinn macht in unserer Sprache. Zumindest wäre nicht klar, ob dieser Ausdruck jetzt ein Wulchenwert oder eine Zahl zurück gibt. Das heißt, wenn wir das Ergebnis dieses Ausdrucks irgendwo alles verwenden, Wulchen wäre nicht klar, wo der jetzt dann verwendet werden kann. Vielleicht noch ein zweites Beispiel von einem Ausdruck, der so keinen Sinn macht. Wenn ich aufschreiben würde, ich möchte den Successor des Ausdrucks if 0, then true als und hier bereche ich dann den predecessor von false, dann wäre das offensichtlich auch ein Ausdruck, der so keinen Sinn macht, denn der predecessor von false ist nicht definiert und damit kann ich natürlich auch dieses komplette if nicht richtig evaluieren und selbst wenn ich jetzt vielleicht da irgendein Wulchenwert raus bekäme, dann würde ich schlussendlich noch den Successor dieses Wulchenwertes berechnen wollen und auch das geht so und der vor mir nicht. Das heißt, das sind im Prinzip Ausdrücke, die so keinen Sinn machen. So, die Frage ist jetzt, wie kriegen wir raus, ob so ein Ausdruck den Sinn macht? Das heißt, ob die Implementierung unserer Sprache diesen Ausdruck tatsächlich evaluieren kann und ob da was ja wohl definiertes, sinnvolles rauskommt. Und ja, da das Thema dieses Moduls Typ Systeme ist, ist die Antwort darauf Typen, denn wir können mit Hilfe von Typen überprüfen, ob ein Ausdruck tatsächlich sinnvoll ist und ob unsere Implementierung der Sprache, wenn sie diesen Ausdruck evaluiert, da auch ein Wert rausbekommt. Und zwar ist die Idee, dass jeder Term oder jeder Ausdruck, den wir aufschreiben können, wenn der einen Typen T hat, also wenn wir den Typen dieses Ausdrucks tatsächlich ausrechnen können, dann wird die Evaluierung dieses Ausdrucks auch nicht unterwegs stecken bleiben, sondern kann tatsächlich rausfinden, was der Wert ist, zudem der Ausdruck evaluiert. Wir schreiben das Ganze dann mit dieser Doppelpunktnotation, also das ist einfach die universell gebräuchliche Form aufzuschreiben, dass irgendetwas einen bestimmten Typ hat, also wir schreiben dann, dass das kleine T unser Ausdruck, das große T als Typen hat, also der Doppelpunkt, den kann man einfach als Hat-Typ lesen. In unserer kleinen Spielsprache soll es zwei Typen geben, nämlich zum einen diesen Typen Nutt, der für natürliche Zahlen verwendet wird, also alle natürlichen Zahlen haben diesen Typen, und zum anderen diesen Typ Bull, der eben für alles verwendet wird, was schlussendlich zu einem bullischen Wert evaluiert, also zu true oder false. Gehen wir am besten nochmal zurück zu unseren Beispielen, um das Ganze noch ein bisschen klarer zu machen. Also für diese ersten beiden Beispiele, die ich hier aufgeschrieben habe, ist es eben nicht möglich, den Typen dieses Ausdrucks zu bestimmen. Im ersten Fall ist das Problem, dass der Ausdruck scheinbar entweder true, also einen bullischen Wert oder eben null, also eine natürliche Zahl zurückgeben kann. Das heißt, wir können den Typen des gesamten Ausdrucks eben nicht eindeutig bestimmen, und damit ist der Ausdruck auch nicht sinnvoll. Im zweiten Beispiel ist das Problem, dass zum Beispiel der predecessor von false schon mal gar nicht definiert ist in unserer Sprache, denn wie wir sehen werden, gibt es einfach keinen Typen, den wir diesem Ausdruck zuordnen können, und damit hat dieser gesamte Ausdruck auch keinen Sinn. Um auch nochmal ein paar positive Beispiele zu gehen, schreibe ich jetzt einfach nochmal zwei andere Ausdrücke auf, die eben einen Typen haben und damit dann auch sinnvoll sind in unserer Sprache. Also ein Beispiel wäre if true, then false, else true. Dieser Ausdruck kann als bull typisiert werden. Das heißt, egal, was da jetzt, ob ich den then branch oder den else branch nehme, in dem Fall ist irgendwie klar, dass ich den then branch nehme, aber aus Typ-Systemesicht wissen wir das ja nicht. Also egal, welcher branch genommen wird, es kommt immer ein bullshit Wert raus, nämlich entweder false oder eben true, und damit ist dieser gesamte Ausdruck dann auch vom Typ bull. Zweites Beispiel ist Folgendes. Ich schreibe einfach den predecessor vom successor von null auf, und da der successor jeweils, wenn ich den successor in eine Zahl berechne, bekomme ich wieder eine andere natürliche Zahl raus, und der predecessor davon ist dann auch wieder eine andere natürliche Zahl. Das heißt, dieser gesamte Ausdruck wird dann vom Typ nut, also für den natürlichen Zahlen sein. Um jetzt genau zu wissen, welcher Ausdruck denn tatsächlich welchen Typ hat und ob ich so einem Ausdruck denn tatsächlich einen Typen zuordnen kann, brauche ich natürlich irgendein Satz von Regeln, und diese Regeln sind genau die Typregeln, die dieses Typ-System dann auch formal definieren. Ich werde die Typregeln für unsere kleine Spielsprache jetzt gleich aufschreiben, bevor ich das mache, vielleicht doch ein kleines bisschen Hintergrundinformation zur Notation. Also was ich hier verwenden werde, ist die Notation, die immer so einen waagrechten Strich in der Mitte hat, und zwar steht da irgendwas zum Beispiel A oben drüber und irgendwas zum Beispiel B unten drunter, und das ist eine sogenannte Regel, und was diese Regel einfach nur sagt, ist, dass wenn das, was oben steht, also in unserem Fall das A der Fall ist, also wenn das wahr ist, dann ist das, was unten steht, auch wahr. Also ist im Prinzip eine einfache logische Implikation. Und das zweite, was ich hier verwenden werde, sieht so ähnlich aus. Das ist auch wieder dieser waagrechte Strich, bloß das oben nichts steht, sondern einfach nur was unten steht, und das ist eine Schreibweise für ein Aktion. Ein Aktion ist einfach etwas, was ohne Bedingung immer gilt. Das heißt, das heißt sozusagen B ist immer wahr. So, mit diesem Hintergrundwissen zur Notation kann ich jetzt die eigentlichen Typregeln aufschreiben, und zwar mache ich das in zwei Gruppen, einmal alle Regeln, die irgendwas mit Bullentypen zu tun hat, und später dann alle Regeln, die was mit den natürlichen Zahlen zu tun haben. Fangen wir an mit den Bullentypen, die eine Regel beziehungsweise sind Aktion. Was ich hier brauche, ist, dass der Wert True diesen Wert Bull hat. Und ich gebe jeder Regel noch einen Namen, die schreibe ich dann hier jeweils so daneben, und das wird später ganz nützlich sein, um zu zeigen, welche Regel wir denn tatsächlich anwenden, wenn wir die mal anwenden. Gleich wie für True, muss ich das Ganze auch für False machen, wo ich sage, ohne irgendeine Bedingung, also wieder ein Aktion, ist False immer vom Typ Bull, und ich nenne diese Regel T False. So, jetzt habe ich die Regeln, die sozusagen uns helfen, True und False zu typisieren. Nun möchten wir das Ganze auch noch für komplexere Ausdrücke machen, zum Beispiel für den Fall, wo dieses if, when, else verwendet wird, und was ich hier jetzt aufschreibe, ist folgendes, ich habe unten dieses if, when, else stehen, und zwar mit irgendeinem Term T1, der als Bedingung fungiert, einem Term T2, der genutzt wird, wenn die Bedingung wahr ist, und einem anderen Term T3, der genutzt wird, wenn die Bedingung falsch sein sollte. Und ich möchte sagen, dass dieser Gesamtausdruck ein Typ T hat, und T ist jetzt sozusagen ein Parameter, also das ist weder Bull noch Nut, sondern eine dieser beiden Typen, und zwar kann ich das sagen, wenn folgende Bedingungen gelten, das wird jetzt nämlich eine Regel, weil ich schreibe oben auch noch was hin, die erste Bedingung ist, dass T1 den Typ Bull haben muss, also wenn dieser Ausdruck T1 kein Bullscher Ausdruck ist, kann ich auch nicht berechnen, ob ich den then oder den else Branch nehmen soll, und außerdem müssen sowohl der Ausdruck T2 vom Typ T sein, als auch der Ausdruck T3. Das heißt, beide Ausdrücke, die ich in dem then oder else Branch nehmen würde, müssen denselben Typen haben, nur dann kann ich sagen, dass dieser Gesamt if, when, else Ausdruck eben diesen Typen T hat. Und diese Regel nennen wir T if. Schauen wir uns als nächstes mal noch die Regeln für die natürlichen Zahlen, also für unseren Typen namens nut an. Auch hier fangen wir wieder an mit einem Aktion, was uns sagt, dass 0 eine natürliche Zahl ist. Das Ding nennen wir T0. Dann schauen wir uns an, welche Typen diese Operatoren, Successor, predecessor und iszero jeweils ergeben. Und zwar sagen wir, dass wenn wir den Successor-Operator auf irgendeinen Term T1 anwenden, dann können wir sagen, dass das einen Wert vom Typ nut zurückgibt, wenn der Term T1 ebenfalls den Typ nut hat. Und diese Regel ist dann die Regel T-Successor. Und so ähnlich machen wir das Ganze auch für den predecessor. Also da wollen wir sagen, wenn ich predecessor auf irgendeinen Term T1 anwende, dann kann ich sagen, dass das Ergebnis davon den Typ nut hat, wenn eben auch dieses T1 den Typ nut hat. Und dann haben wir noch einen Operator in unserer Sprache, für den wir jetzt noch keinen Typ definiert haben. Und das ist iszero. Und was wir hier rausbekommen möchten, ist, dass iszero von irgendeinem Typ, sorry, von irgendeinem Term den Typ pull hat. Und zwar genau dann. Wenn dieser Term T1 eine Zahl ist, denn ich kann nur checken, ob etwas null ist, wenn das natürlich auch eine Zahl ist, weil die Frage, ob false jetzt die Zahl null ist oder nicht, macht zumindest in unserer Sprache hier keinen Sinn und sollte von daher auch nicht Typ korrekt sein. Das heißt, die Bedingung hier ist, dass T1 vom Typ nut ist und diese Regel nennen wir T iszero. So, und was wir jetzt mit Hilfe dieses Satzes von Regeln machen können, ist im Prinzip für jede beliebige Ausdruck, den wir in unserer kleinen Spielsprache aufschreiben können, zu überprüfen, ob es tatsächlich ein Ausdruck ist, der einen eindeutig bestimmten Typ hat. Und wenn das so ist, dann wissen wir, dass wir den Ausdruck dann auch vollständig evaluieren können. So, um das jetzt zu machen, also um tatsächlich zu überprüfen, ob ein Ausdruck einen Typ hat und wenn ja welchen, müssen wir diese Ausdrücke type checken. Und das wird so gemacht, dass wir eine Relation berechnen, die sich typing relation ent. Das klingt jetzt kompliziert, und im Prinzip, was das nur bedeutet, ist, wir versuchen, jedem Term einen Typen zuzuordnen, sodass diese Zuordnung mit allen Regeln, die wir hier verwenden, kompatibel ist. Und insbesondere versuchen wir dabei, die kleinste solche Relation zu finden. Das heißt, wir versuchen, das mit der minimalen Anzahl an Regelanwendungen zu tun. Wenn wir das machen können, das heißt, wenn wir zeigen können, dass dieser Term tatsächlich einen Typ hat, dann sagen wir, er ist typeable oder manchmal sagt man auch well typed. Also das ist der Fall, wenn wir zeigen können, dass unser Term klein T eben irgendein Typ hat. Und zwar können wir dann auch aufschreiben, dass T eben diesen Typ hat, entsprechend unserer Regeln. Und wenn wir das machen können, dann können wir, und jetzt kommt noch ein dritter Begriff, die sogenannte type derivation aufschreiben. Und das ist einfach nur ein Baum von Regelanwendung, der schlussendlich zeigt, dass der Term klein T den Typen groß T hat. Schauen wir uns das Ganze am besten mal am Beispiel an. Und zwar schauen wir uns hier so eine konkrete type derivation oder Typ-Ableitung an. Und zwar machen wir das Ganze mit zwei Beispielen. Und hier kommt jetzt erstmal Beispiel 1. Das ist ein Beispiel, bei dem wir zeigen können, dass der Term, den wir uns hier anschauen, tatsächlich einen Typen hat. Und zwar ist das das Beispiel, was wir vorhin schon mal so gesehen haben, nämlich if true, then false, as true. Und wir wollen jetzt zeigen, dass dieser Term einen Typ hat. Wenn wir uns unsere Regeln jetzt anschauen, gehe ich mal kurz zurück, dann müssen wir quasi eine Regel finden, die zu dieser Konklusion führt, dass if irgendwas, wenn irgendwas, als irgendwas irgendeinen Typen hat. Und das ist diese Tif-Regel, die wir hier unten links sehen. Und wenn wir die hier anwenden, müssen wir also drei Dinge zeigen. Nämlich, dass T1 den Typ bool hat und T2 und T3 denselben Typen haben. Das heißt, was wir hier konkret zeigen müssen, ist, dass true den Typ bool hat und dass false, also unser T2, einen bestimmten Typen hat und auch true diesen bestimmten Typen hat. Und das wird dann der Typ sein, zudem der Gesamtausdruck evaluiert. Und das ist die Regel Tif, die wir hier anwenden wollen. Gehen wir diese drei Sachen mal durch. Wir sind noch nicht ganz fertig für das erste Teil. Also um zu zeigen, dass true tatsächlich vom Typ bool ist, können wir ganz einfach dieses eine Aktion, was wir haben, nämlich true verwenden. Denn das zeigt uns im Prinzip genau das. Und damit sind wir an der Stelle des Baumes auch schon fertig. Ebenso können wir für false zeigen, mithilfe von T false, dass der Typ von false bool ist. Und damit das Ganze jetzt aufgeht, muss jetzt hoffentlich der zweite, also der dritte Term hier auch noch vom Typ bool sein. Und das ist er denn hier, können wir wieder T true verwenden, was uns dann schlussendlich zeigt, dass true vom Typ bool ist. Das heißt, dieser Gesamtausdruck wird dann ebenfalls zum Typ bool typisiert. Und das heißt, wir können an der Stelle zeigen, dass unser Ausdruck tatsächlich einen Typen hat. Und somit wissen wir, dass das ein valider und sinnvoller Ausdruck ist. Der schlussendlich auch zu einem Wert evaluiert werden kann. So, dann schauen wir uns mal noch ein zweites Beispiel an. Und bei diesem zweiten Beispiel werden wir eben nicht in der Lage sein, diese Type Derivation komplett aufzuschreiben, weil der Ausdruck eben kein eindeutigen Typen entsprechend unsere Typregeln hat. Fangen wir wieder mit dem Baum ganz unten an. Also mit dem, was wir schlussendlich zeigen wollen. Der Ausdruck, den wir hier anschauen, ist folgender. Wir wollen wissen, ob der Successor von if null, when true, else predecessor von false einen Typen hat. Und ja, versuchen wir uns einfach, das mithilfe der Regeln, die wir hier haben, zu tun. Für diesen der äußerste Operate, die hier verwendet wird, ist der Successor-Operate. Das heißt, wir müssen hier an der Stelle jetzt versuchen, die T-Successor-Regel zu verwenden. Und wir wissen von unseren Regeln, dass die auf jeden Fall zeigen muss, dass das Ganze vom Typ nut ist. So, um das zeigen zu können, müssen wir zeigen, dass all das, was, vielleicht noch eine Klammer, dass all das, was an diesen Successor-Operator übergeben wird, ebenfalls vom Typ nut ist. Das heißt, was wir hier zeigen müssen, ist, dass if null when true else predecessor von false dass dieser gesamte Ausdruck den Typ nut hat. Und jetzt ist die Frage, können wir das zeigen? Wir sehen, rein so taktisch, dass das wieder so ein if-then-else ist. Das heißt, die Regel, die wir hier anwenden müssen, ist tif. Und jetzt sind wir an so einer ähnlichen Stelle, und jetzt müssen wir nämlich zeigen, dass das, was wir hier vorne haben, der erste Ausdruck vom Typ bool ist, denn nur dann können wir tatsächlich zeigen, dass dass wir diese if-then-else Regel anwenden können. Und damit schlussendlich der Gesamtausdruck zu nut typisiert ist, müssen wir auch zeigen, dass der zweite Ausdruck, also dieses zweite, dieses true hier, dass das den Typ nut hat. Und außerdem müssen wir dann noch zeigen, dass der predecessor von false ebenfalls den Typ nut hat. So, und für jedes dieser müssen wir jetzt entweder ein Axiom oder eine Regel finden. Und das Problem ist, dass null natürlich nicht vom Typ bool ist, also wenn wir nochmal zu den Regeln zurückgehen, dann sehen wir das Einzige, was so als literal direkt zu bool typisieren können ist true und false, aber eben nicht null. Das heißt, an der Stelle stecken wir fest und es geht eigentlich nicht weiter. Und das gleiche gilt für diesen zweiten Teilbaum, den wir hier haben, denn auch hier haben wir keine Regel, die uns erlaubt zu sagen, dass true den Typ nut hat. Das heißt, wir können an der Stelle keine vollständige Type Derivation aufschreiben, oder keine weitere Regel hier anwenden, obwohl wir bis hierhin alles so gemacht haben, wie es eben nur ging. Und das heißt, insgesamt ist der Ausdruck, der ganz unten steht, eben nicht well timed. So, und was wir damit wissen ist, dass dieser Ausdruck tatsächlich nicht sinnvoll ist und können das jetzt auch formal mit Hilfe des Typsystems zeigen. Das heißt, wir müssen auch gar nicht versuchen, diesen Ausdruck dann tatsächlich zu evaluieren, denn wir werden nicht in der Lage sein, den Wert dieses Ausdrucks tatsächlich zu evaluieren. So, als kleines Quiz, ob das Ganze jetzt insgesamt Sinn macht, habe ich hier mal einen weiteren Ausdruck in unsere Spielsprache und ihre Aufgabe soll sein, die passende Type Derivation dazu zu finden. Also diesen Baum an Regelanwendungen, wie ich das gerade eben auch gemacht habe, versuchen aufzuschreiben, um dann hoffentlich feststellen zu können, dass dieser Ausdruck einen bestimmten Typen hat. Und die Frage, die Sie dann bitte, um Faktiome und Regeln braucht man dann, um das Ganze zu machen. Das ist also so ein kleiner Hash-Wert quasi, um zu schauen, ob Ihr Baum wahrscheinlich richtig aussieht. Also bitte an dieser Stelle das Video anhalten und das Ganze mal selbst machen, denn Zuschauen ist immer schön und gut, aber das selber machen hilft wahrscheinlich dann doch am meisten. Ja, schauen wir uns mal die Lösung dazu an. Also die Aufgabe war ja diesen Ausdruck if false then predecessor von null else successor von null zu typisieren. Das heißt, wir wollen hier einen bestimmten Typen-Schluss endlich rausbekommen. Jetzt geht es wieder erstmal von außen nach innen. Syntaktisch ist das äußerste 1if. Das heißt, die Regel, die wir hier verwenden müssen, ist die Tif-Regel. Die Regelung, das erste ist, dass das, was ganz vorne steht, nämlich in unserem Fall, das false vom Typ bool ist und glücklicherweise gibt es da ein passendes Aktion zu. Das heißt, wir können mit diesen T-Faults das schon mal abhaken, wunderbar. Dann müssen wir zeigen, dass der Ausdruck, der beim Venbranche rauskommt, also in unserem Fall predecessor von predecessor von null eine bestimmten Typ hat und wir müssen zeigen, dass das, was beim Venbranche rauskommt, nämlich in unserem Fall successor von null auch denselben Typen hat. Noch wissen wir nicht genau, welcher Typ das ist, aber werden wir hoffentlich bald rausfinden. Für die, schauen wir erstmal den Baum in der Mitte an, also um rauszufinden, welchen Typ das hat, schauen wir uns den entsprechenden Regel an, die wir haben für den predecessor Operator und das ist T predecessor und das legt fest, dass predecessor immer nur ein Wert vom Typ nut ergeben kann. Das heißt, wir wissen schon, dass hier und auch hier schlussendlich nut rauskommen muss. Machen wir erstmal trotzdem in der Mitte weiter, also um das zeigen zu können, müssen wir zeigen, dass das Argument, was wir an diese predecessor diesen Operator übergeben, ebenfalls vom Typ nut ist. Das Argument hier ist T predecessor von null und um zeigen zu können, dass das vom Typ nut ist, müssen wir zeigen, dass null vom Typ nut ist. Das ist wieder diese T predecessor und um zeigen zu können, dass null vom Typ nut ist, können wir das entsprechende Axiom verwenden. Damit sind wir in der Mitte auch schon fertig, jetzt schauen wir noch rechts an, also da müssen wir zeigen, dass der successor von null nut ist, das können wir machen, wenn wir zeigen können, dass null den Typ nut hat und hier sind wir wieder bei dem selben Axiom, wie gerade eben schon angelangt und sind damit auch fertig. Das heißt, wir haben den Typ-Ableitungsbaum komplett aufgeschrieben und können somit zeigen, dass dieser Gesamtausdruck vom Typ nut ist und um jetzt noch die Frage, die im Ilias war zu beantworten, dafür braucht man also drei Axiome und die Verwendung von Regeln und damit ist der Baum dann noch vollständig. So, jetzt haben Sie gesehen, wie man so ein Typsystem eigentlich mal formal aufschreiben kann und wie man dann auch zeigen kann, ob ein gegebenes Stück Code ein Programm oder in unserem Fall ein Ausdruck einen bestimmten Typ hat und überhaupt Typ korrekt ist. In manchen Sprachen muss man ja wie gesagt nicht alle Typen anotieren als Programmierer und ein Weg, wie diese Typen dann trotzdem noch gefunden werden können, ist das mein Typen inferiert. Das Ganze passiert in Sprachen, die statisch typisiert sind, aber wo die Programmierer eben bestimmte Typen weglassen können und diese Sprachen bieten dann bestimmte Garantien, nämlich dieselben Garantien, die auch Static Type Checking normalerweise anbietet, eben ohne, dass man alle Typen anotieren muss. Das heißt der Programmierer muss weniger Typen anotieren, hat aber trotzdem dieselben Garantien wie in einer komplett statisch typisierten Sprache, wo man alle Typen hinschreibt. Was wichtig ist zu verstehen, ist, dass das anders ist als Gradual Typing, wo der Programmierer selbst entscheidet, wann und ob er bestimmte Typen anotiert. In Sprachen mit TypInference müssen statisch alle Variablen und alle Ausdrücke einen festen berechneten Typen haben, der entweder eben inferiert wurde oder vom Programmierer explizit anotiert wurde. Eine Sprache, die das sehr gut implementiert, ist Scala und deswegen habe ich hier mal ein kleines Scala Beispiel, um diese TypInference zu zeigen. Also was wir hier machen, ist zum einen eine Variable definieren, in die wir diesen Stringwert reinschreiben und obwohl wir diese Variable eben jetzt nicht explizit einen Typen zuordnen, wird der Scala Compiler das für uns tun, also der Scala Compiler wird inferieren, dass diese Variable Business Name den Typen String hat. Außerdem haben wir hier unten eine Funktion definiert, und zwar die Funktion square off, die einen Wert x nimmt und wir anotieren explizit, dass dieser Parameter, den wir hier reingehen müssen, vom Typ int sein muss. Was wir dann aber nicht anotieren, ist der Rückgabetyp dieser Funktion, sondern hier steht einfach nur, dass diese Funktion x mal x ausrechnet und das dann auch zurückgibt. Und was der Scala Compiler dann für uns macht, ist zu inferieren, dass der Rückgabewert also auch ein int ist, weil wenn man zwei ints multipliziert, kommt auch wieder ein int raus. Und mithilfe dieser TypInformation, die jetzt teilweise durch Anotation gegeben ist, teilweise aber auch einfach nur inferiert wurde vom Compiler kann schlussendlich für diesen Aufruf dieser Funktion hier unten ein CompileTime TypFehler ausgegeben werden, denn wir wissen jetzt, dass dieses square off von der Zahl 23 ein int zurückgeben wird, und wir wissen aber auch, dass BusinessName den Typ String hat, und das TypSystem von Scala erlaubt nicht Integers in String-Variablin zu schreiben, und deswegen ist das ganze hier TypIn korrekt und wir bekommen den entsprechenden TypFehler. Wie genau diese Typ-Uferenz jetzt implementiert wird, ist ein super spannendes Thema, aber übersteigt definitiv diese Veranstaltung, deswegen wenn wir das nicht weiter erläutern, aber stattdessen zum Abschluss dieses Moduls zum Thema TypSysteme noch ein kleines Quiz haben, und zwar wieder in der Form von vier Sätzen, die ich aufgeschrieben habe, von denen manche korrekt sind, und manche nicht. An der Stelle dann bitte wieder das Video kurz anhalten, und über diese Sätze nachdenken, in Ilias um, ob die jetzt stimmen oder nicht, und erst danach weiterschauen. Schauen wir die Lösung mal an, also die ersten drei Sätze sind alle falsch, der vierte stimmt allerdings. Der erste Satz hatte gesagt, dass Typen um kompatibel zu sein gleich sein müssen, aber was wir ja gesehen haben, ist dass Kompatibilität von Typen eben nicht Gleichheit und auch nicht Equivalenz erfordert, sondern dass oft Typen ineinander konvertiert werden können, und damit kompatibel sie eben nicht Gleichheit sind. Der zweite Satz stimmt auch nicht, denn er sagt, dass Coercions, also implizierte Typkonvertierung bedeuten, dass ein Programmierer einwert von einem Typ zu einem anderen Typ castet, aber eben das passiert hier nicht, sondern diese Coercions sind implizit, das heißt, der Programmierer muss nicht casten, sondern das macht die Sprachimplementierung automatisch. Der dritte Satz hat behauptet, dass Typkonvertierung immer garantieren, dass der die Bedeutung eines Wertes erhalten bleibt, aber das stimmt leider nicht, denn zum Beispiel haben wir ein Ziel gesehen, dass man ja Zahlen oder auch Werte, die in größeren Mengen von Bits gespeichert sind, konvertieren kann in kleineren Repräsentationen, und dabei gehen oft Informationen verloren, zum Beispiel in denen die Nachkommastellen von einer Floating Pointzahl abgeschnitten werden. Der vierte Satz stimmt, manchmal stimmt auch was, zum Beispiel dieser Satz, der sagt, dass Programmiersprachen die Typinferenz anbieten, wie zum Beispiel das, wie zum Beispiel Scala, was wir hier gerade gesehen haben, statische Typgarantien anbieten können, und das ist korrekt. Also in diesen Sprachen werden Typen, die nicht anotiert werden, inferiert, und nur wenn das möglich ist, ist das Programm dann auch Typkorrekt und wird vom Thema als solches dann akzeptiert. Ja, und dann sind wir am Ende des Moduls zum Thema Typsysteme. Ich hoffe, Sie haben jetzt einen besseren Eindruck davon, warum man Typ überhaupt in Programmiersprachen hat, wie die da konkret verwendet werden, und was es da alles für verschiedene Arten und Weisen gibt, diese Typen in Programmiersprachen zu integrieren. Wir haben auch ein bisschen angeschaut, wie Typsysteme formal aufgeschrieben werden, sodass man dann formal zeigen kann, dass ein Stück Kot tatsächlich einen Typ hat und damit auch sinnvoll ist, sodass Sie auch die Blickweise auf Typsysteme zumindest mal ein bisschen kennengelernt haben. Damit vielen Dank fürs Zuhören und bis zum nächsten Mal.