 Ja, herzlich willkommen zu Programmiert Paradigmen. Wir sind immer noch im ersten Themenblock, also der Einführung in diese Veranstaltung. Und wollen uns jetzt ein bisschen genauer mit der Thematik, um die es hier eigentlich geht, befassen und ich gebe einfach einen kleinen Überblick darüber, worum es in der Veranstaltung insgesamt gehen soll. Ja, also fangen wir vielleicht erstmal mit ein bisschen Geschichte. Also wenn man sich anschaut, wie die ersten elektronischen Computer programmiert wurden, dann haben Programmiersprachen da eigentlich noch gar keine Rolle gespielt. Denn die Programmierer haben die Algorithmen oder Ideen, die sie gerne ausdrücken wollten, damals noch als Sequenzen von Bits kodiert, die genau in der Maschinsprache geschrieben waren, die eben der jeweilige Computer verstand. Ich hab hier mal ein kleines Beispiel, wo man ja die Berechnung vom größten, gemeinsamen Teil der Zweierzahlen sieht. Und wie man sieht, versteht man das im Prinzip nicht. Außer man weiß jetzt genau, welche Art Computer und welche Maschinsprache da dahinter steckt. Aber das waren eben so die Anfänge der Programmierung, also im Prinzip gab es damals noch keine Programmiersprache. Der Grund, warum das Anfang so war, ist, dass die Maschinen gebraucht haben, um diese noch relativ einfachen Algorithmen auszuführen, einfach viel, viel länger war, als es heute ist. Und damals noch so wertvoll war, dass die Zeit, die die Maschinen gebraucht haben, viel wertvoller war, als die Zeit, die die Programmierer gebraucht haben, um die Maschinen zu programmieren. Und somit war einfach noch nicht, war es einfach noch nicht besonders wichtig, bessere Sprachen zu haben, um Algorithmen auszudrücken. Das hat sich dann relativ schnell geändert und statt direkt in der Maschinsprache zu schreiben, ist man dann relativ bald dazu übergegangen, stattdessen für Menschen einfacher zu lesende Formen von diesen Maschineninstruktionen zu verwenden, nämlich Assembly. Assembly ist immer noch sehr, sehr nah an der Maschinsprache. Im Prinzip sprechen die Instruktionen, die man in Assembler hat, ungefähr dem, was auch tatsächlich auf der Maschine abläuft. Aber es wird eben in der Art und Weise aufgeschrieben, die für Menschen deutlich einfacher zu lesen und auch schreiben sind, als das, was wir vorher gesehen haben. Hier ist mal das selbe Beispiel, wie gerade eben, also wieder der Algorithmus zum Berechnen des größten gemeinsamen Teilers zweier Zahlen, diesmal in X86 Assembler. Und was man hier sieht, ist, dass man bestimmte Dinge doch schon einfacher verstehen kann. Also zum Beispiel sieht man hier bei dieser Push Instruktion, dass da irgendein Wert in ein Register geschrieben wird oder hier sieht man zum Beispiel, dass irgendwas substrahiert wird oder hier sieht man, dass irgendeine andere Funktion aufgerufen wird. Das Ganze hat den Vorteil im Vergleich zum direkten Hinschreiben der Maschineninstruktion, dass es natürlich viel weniger fehleranfällig ist. Aber es ist natürlich trotzdem immer noch sehr, sehr nah an der Maschine, auf der Algorithmusschluss endlich ausgeführt wird. Und es hat einen großen Nachteil, nämlich, dass ich jedes Mal, wenn eine neue Generation von Computern rauskomme, entsprechend natürlich die Instruktion, die dieser Computer verarbeiten kann, auch wieder anders ist. Und ich dann mein Programm neu schreiben muss, nämlich in der neuen Assembly Sprache, die eben diese neue Generation von Computern versteht. Der andere Nachteil insbesondere, wenn man daran denkt, dass Systeme vielleicht ein bisschen komplexer werden sollen als dieser kleine Algorithmus, den wir hier als Beispiel sehen, ist, dass der Entwickler natürlich immer noch in sehr, sehr low-level Operationen über das Problem nachdenken muss und darüber nachdenken muss, wie genau das jetzt auf der Hardware ausgeführt wird, anstatt sich vielleicht mehr mit den mathematischen oder logischen Konstrukten dahinter beschäftigen zu können. Zum Glück ging es dann relativ bald weiter, nämlich in Richtung von sogenannten High-Level-Programmiersprachen. Und das sind dann auch die Sprachen, um die es hier in der Veranstaltung hauptsächlich gehen soll. Das ging so ungefähr in den 50er Jahren los mit Sprachen wie Fortran und Lisp und Algol. Das waren so die ersten richtigen Programmiersprachen. Und was das revolutionelle Neue an diesen Sprachen damals war, ist, dass der Programmierer oder Entwickler der Software eben nicht mehr in den Maschininstruktionen denkt, sondern in mathematischen oder logischen Abstraktion. Schauen wir nochmal das selbe Beispiel, was wir gerade eben schon hatten, diesmal mit Fortran als der Sprache der Wahl an. Und was man hier sieht, ist, dass es schon sehr, sehr nah an dem ist, was Sie vermutlich auch als Programmierenden kennen. Also wir haben da so eine Funktion, die hier aufgerufen wird, die Funktion bekommt Argumente übergeben. Dann gibt es da noch weitere Variablen, die definiert werden. Dann haben wir eine Schleife, ein paar Zuweisungen, mathematische Operationen. Und mithilfe all dieser Dinge wird der Algorithmus beschrieben, ohne dass man genau beschreibt, wie das Ganze jetzt auf dem Computer tatsächlich ausgeführt wird. Sondern man hat eine Sprache, die auf einem höheren Abstraktionslevel tatsächlich arbeitet. Heutzutage gibt es natürlich nicht nur diese paar Sprachen, die da am Anfang so Pionierarbeit geleistet haben, sondern es gibt mittlerweile Tausende von Programmiersprachen. Und es ist keine Übertreibung, es gibt wirklich Tausende, auch wenn Sie vielleicht viele davon jetzt nicht kennen. Und die Menge an Programmiersprachen entwickelt sich auch immer weiter. Es gibt regelmäßig neue Sprachen, die dazukommen, zum Beispiel weil eine neue Art Plattform oder eine neue Art von System plötzlich relevant ist, an die die Leute früher gar nicht gedacht haben. Und deswegen werden auch ständig neue Sprachen entwickelt. Gleichzeitig gibt es natürlich eine ganze Reihe von Sprachen, die schon seit Jahrzehnten oder vielleicht noch viel länger relevant sind. Fortran, Kobol und C sind Beispiele dafür, die jeweils ihre Nische oder ihre Berechtigung haben, obwohl es die eigentlich schon seit vielen Jahrzehnten gibt. Fortran ist zum Beispiel immer noch sehr, sehr beliebt für High Performance Computing, also alles, was mathematische Berechnungen ausführt und sehr schnell laufen soll. Kobol wird in der Finanzindustrie viel verwendet, unter anderem, weil die einfach historisch sehr, sehr viele Programme in Kobol haben. Und man die Systeme, die große Geldmengen verwalten und hin und her schieben, jetzt mal nicht über Nacht einfach auf eine neue Sprache umstellen möchte, sondern solange das irgendwie läuft, lässt man es halt laufen. Und C zum Beispiel wird sehr viel in Systeme nach Programmierung verwendet, einfach weil man die Vorteile einer High-Level-Sprache verbindet mit doch relativ mächtigen Konstrukten, um zu beschreiben, wie der Code tatsächlich auf der Hardware ausgeführt wird, indem man halt über Speicher und die ähnliche Dinge genau verwalten kann. Was mich persönlich interessiert, ist welche Sprache sie denn eigentlich so als ihre Lieblingssprache bezeichnen würden. Und um das mal zu erfahren, habe ich so eine kleine Umfrage in Ilias, die wir auch gleichzeitig mal als Test für die Quizzes, die so in der Veranstaltung dann öfters noch geben wird, nutzen können. Ich werde nämlich jetzt dieses kleine Symbol hier anzeigen, dieses da. Und was das bedeutet, ist, dass Sie bitte das Video an der Stelle mal anhalten, anschließend zum Ilias gehen und diese kleine Umfrage machen, wo Sie mir einfach mal sagen, was ist denn Ihre Lieblingsprogrammiersprache. Und ja, dann wissen Sie, wie das geht und wir können das dann für die Quizzes, die im Rest der Veranstaltung noch folgen, genauso machen. Ein Grund, warum ich das interessiert, was Ihre Lieblingssprache ist, ist, dass wir natürlich die Veranstaltung dann auch entsprechend immer ein bisschen anfassen. Also wenn wir sehen, dass großes Interesse an einer bestimmten Sprache existiert, die wir bis jetzt vielleicht gar nicht so drin haben, dann können wir das natürlich gerne hinzufügen. Und dafür muss ich halt einfach erst mal wissen, was denn Ihre Lieblingssprache ist. Also stimmen Sie da bitte jetzt mal ab. Gut, so, jetzt hat jeder seine Lieblingssprache und eine interessante Frage ist natürlich, warum ist denn eine Sprache überhaupt beliebt? Oder warum ist eine Sprache erfolgreich und wird von vielen Leuten für verschiedenste Arten von Anwendung verwendet? Da gibt es nicht nur eine Antwort, sondern eine ganze Reihe von Gründen, warum Sprachen erfolgreich sind, die ich hier mal ein bisschen aufgelistet habe. Also das eine ist natürlich erstmal die Ausdrucksstärke. Im Prinzip kann man mit jeder Sprache die Touring vollständig ist, genau dasselbe ausdrücken. Jeder Algorithmus, der in einer der Sprachen ausdrückbar ist, ist auch in den anderen Sprachen ausdrückbar. Das heißt, eigentlich bietet keine Sprache dann wirklich einen Vorteil, aber praktisch gibt es diese Vorteile natürlich doch, weil bestimmte Arten von Algorithmen in bestimmten Arten von Sprachen einfach besser ausdrückbar sind. Ein anderer Punkt, der sehr wichtig ist, gerade am Anfang, wenn man anfängt zu programmieren ist, wie einfach ist es denn eigentlich, diese Sprache zu lernen? Es gibt Sprachen wie zum Beispiel Basic oder Python, die bekannt dafür sind, dass sie relativ einfach zu erlernen sind und deshalb gerade von Anfängen sehr, sehr gern als erste Sprache genutzt werden. Python wird natürlich trotzdem auch noch von vielen anderen Leuten verwendet und anderen zum Beispiel von mir für bestimmte Arten von Projekten. Also das ist keine reine Anfängersprache, sondern auch eine Sprache, die in verschiedensten anderen Bereichen sehr, sehr populär ist. Ein dritter wichtiger Punkt ist, wie die Sprache überhaupt verfügbar gemacht wird. Also gibt es diese Sprache zum Beispiel als Open Source, ist die Sprache selbst erweiterbar oder ist zumindest die Implementierung der Sprache in Form von Compiler, zum Beispiel, von anderen erweiterbar. Und wenn die Antwort Ja ist, dann hilft das oft, die Sprache populärer und erfolgreicher zu machen. Ganz einfach, weil viele Leute dann die Möglichkeit haben, diese Sprache ohne irgendwelche Hindernisse zu benutzen und gegebenenfalls auch an ihre Zwecke weiter anzupassen. Eine andere Punkt ist Standardisierung. Wenn die Sprache standardisiert ist, also wenn aufgeschrieben ist, formal oder vielleicht auch weniger formal, was eigentlich alles Teil der Sprache ist und was diese Sprachfeatures eigentlich genau bedeuten, dann hat es den riesengroßen Vorteil, dass jeder weiß, was es bedeutet, wenn man ein Programm in der Sprache hinschreibt und das somit auch die Potabilität auf zukünftige Plattformen gewährleistet ist, weil man nämlich dann weiß, was diese Sprache bedeutet und dieses Verhalten dann entsprechend auch für zukünftige Plattformen wieder implementieren kann. Eine andere sehr wichtige Punkt ist natürlich, ob es gute Werkzeuge und insbesondere gute Compiler für die Sprachen gibt. Also ein Grund, warum Fortran immer noch so beliebt im Halb Performance Computing ist, ist, dass es ganz einfach sehr, sehr gute Compiler dafür gibt. Dies schaffen aus dem Code, der in der Vortan geschrieben ist, wirklich das letzte Müh Performance rauszuholen und das ist was im Halb Performance Gebiet einfach zählt und deswegen ist die Sprache, auch wenn sie vielleicht aus sprachtechnischer Sicht eine ganze Reihe von Macken hat, immer noch sehr beliebt, denn es gibt einfach richtig gute Tools dafür. Schlussendlich gibt es noch eine ganze Reihe von anderen Faktoren, die ich hier mal so unter Economics zusammengefasst habe und erzählen z.B. so Dinge rein, wie wird die Sprache von einem größeren Unternehmen oder von einer größeren Organisation unterstützt und Sprachen wie C-Sharp z.B. sind unter anderem deshalb so erfolgreich, weil sie eben von Microsoft gepusht werden oder ähnlich für Objective C und Apple. Gut, wenn man sich so das Spektrum von Programmiersprachen anschaut, dann gibt es da verschiedene Wege, wie man das ein bisschen sortieren kann. Also eine sehr grobe Einteilung ist, dass es deklarative Sprachen und imperative Sprachen gibt. In der deklarativen Sprache fokussiert sich der Programmierer mehr darauf zu beschreiben, was eigentlich berechnet werden soll und gegen eine imperativen Programmiersprache der Fokus darauf ist, wie denn diese Berechnung stattfinden soll. Beispiele für deklarative Sprachen sind Haskell, eine funktionale Sprache oder SQL, in der ich Queries formuliere, um Daten aus Datenbanken zu extrahieren oder auch Spreadsheets, also wenn sie in Excel oder einem anderen Spreadsheet-Tool ein Spreadsheet beschreiben und zum Beispiel sagen, dass diese Spalte das Produkt aus dem Wert in der Spalte und der Spalte sein soll, dann schreiben sie eigentlich ein Programm und zwar in einer deklarativen Programmiersprache, auch wenn man die Sündtexteils nicht genauso hinschreibt, wie man das vielleicht in vielen anderen Sprachen tut, ist das schlussendlich auch eine Programmiersprache. Imperative Sprachen hat sicherlich jeder schon mal irgendwie hier benutzt, das sind Dinge wie C und Java oder Pearl und Python und so weiter, wo man Befehle in einer bestimmten Reihenfolge angibt, die dann sagen, wie genau die Berechnung von Statten gehen soll. Egal ob jetzt deklarativ oder imperativ, gibt es eine ganze Reihe von Programmiersprachenparadigmen, die in verschiedensten Formen vermischt werden in die verschiedenen Sprachen. Also ich habe hier mal ein paar aufgelistet, Funktional, Logik, sequenziell, statisch typisiert, dynamisch typisiert, für nebenläufige oder parallele Software gibt es Shared Memory Parallel oder Distributed Memory Parallel und all diese Paradigmen beschreiben im Prinzip in einer Dimension, welche Designentscheidungen man als Sprachentwickler treffen kann und was die meisten Sprachen machen, ist jetzt nicht nur eins dieser Paradigmen zu implementieren, sondern verschiedene Paradigmen in der einen Form oder der anderen Form miteinander zu kombinieren. Um es ein bisschen konkreter zu machen, schauen wir uns jetzt mal ein paar Beispiele an, und zwar schauen wir uns dieses Programm, das wir jetzt ja vorhin schon in Maschinsprache, Assempler und Vortragen gesehen haben, nochmal in anderen Programmiersprachen an, nämlich wieder die Berechnung des größten gemeinsamen Teiles zweier Zahlen. Hier ist jetzt mal ein Beispiel für eine imperative Sprache, nämlich C, in der das Ganze implementiert ist. Wir haben also eine Funktion, die kriegt diese zwei Zahlen A und B und berechnet jetzt den größten gemeinsamen Teilen mithilfe von Statements, die jeweils andere Statements beeinflussen. Also konkret wird zum Beispiel in diesem Wild Statement bestimmt, ob dieses if-else-Statement ausgeführt wird und wenn ja, wie oft, oder dieses if-else-Statement bestimmt, ob dieses Assignment, was hier dahinter kommt, ausgeführt wird. Zum Thema Assignments, die sind bei imperativen Programmiersprachen natürlich ganz, ganz wichtig, und was die konkret machen, ist, dass die einen Seiteneffekt haben auf die zukünftige Ausführung beeinflussen, indem sie eben den Speicher, ein Stück Speicher schreiben und damit bestimmen, was in dem Rest der Ausführung noch passiert. Also dieses Assignment hier zum Beispiel wird dann wiederum bestimmen, wie diese Bedingung dieser Wildschleife bei der nächsten Iteration ausgehen wird. Das heißt, dieses Assignment hat den Seiteneffekt, dass es eben bestimmt, wo genau die Ausführung anschließend noch lang geht. So, das war jetzt ein Beispiel für eine imperative Sprache. Schauen wir uns mal ein Beispiel für eine funktionale Sprache an, und zwar O'Cable. Was wir hier sehen, ist auch wieder eine Funktion, die unseren Algorithmus für den größten gemeinsamen Teile implementiert. Genau das Albumprogramm wie vorher, bloß anders hingeschrieben. Da vielleicht nicht jeder O'Cable genau kennt, erkläre ich kurz die Sündung ein bisschen. Also was wir hier sehen, ist einfach eine Definition einer Rekosiven Funktion mit zwei Parametern. Das Lett sagt einfach nur, dass wir jetzt was deklarieren. Rek gibt an, dass das Ganze eine Rekosive Funktion ist, dann hat ihr einen Namen und bekommt anschließend noch die zwei Argumente übergeben. In dem Body der Funktion selbst sehen wir dann, dass der Fokus nicht so sehr auf den einzelnen Instruktion und in welcher Reihenfolge die ausgeführt werden liegt, sondern stattdessen auf den mathematischen Relation zwischen den Eingaben, die diese Funktion bekommt und den Ausgaben, die sie produzieren sollen. Und zwar funktioniert das so, dass der Kot im Prinzip sagt, wenn bestimmte Bedingungen auftreten, dann ist das Ergebnis dieser Funktion einfach dieses oder jenes. Also in diesem Fall, zum Beispiel, sagen wir, das Ergebnis ist A oder in diesem Fall sagen wir, das Ergebnis ist das, was wir bekommen, wenn wir dieselbe Funktion jetzt nochmal aufrufen, und zwar mit B und A minus B. So, als drittes Beispiel schauen wir uns das Ganze nochmal in Prolog an. Das ist eine Logikprogrammiersprache, in der man auch den größten gemeinsamen Teil als bei Erzahlen berechnen kann. Und zwar funktioniert das so, dass wir hier eine Menge von Fakten und Regeln angeben. Also jede dieser Zeilen ist in dem Fall eine Regel. Und zwar sagt die, dass das, wenn das, was hier hinten steht, wahr ist, dann gilt auch das, was hier vorne steht. Dieses Komma ist einfach ein logisches Und. Also was hier steht, ist im Prinzip, wenn A gleich B ist und G gleich A ist, dann ist der größte gemeinsame Teiler von A und B gleich G. Wenn man sich das also anschaut, ist hier der Fokus auf den logischen Relation zwischen den verschiedenen Variablen. Also wir drücken alles im Prinzip durch reine Logik aus und können damit aber auch Algorithmen implementieren, zum Beispiel eben diesen GCD-Algorithmus, den wir jetzt eigentlich schon mehrfach gesehen haben. So, jetzt haben wir uns angeschaut, wie genau Programme überhaupt aufgeschrieben können. Also welche Arten von Programmiersprachen es denn so gibt. Jetzt würde ich noch einen kleinen Überblick darüber geben, wie die Programme schlussendlich ausgeführt werden, denn die Implementierung von Programmiersprachen ist mindestens genauso wichtig, wie die Programmiersprachen selbst. Im Grunde gibt es ungefähr drei Wege, wie Programmiersprachen implementiert werden heutzutage. Das eine ist über reines Kompilieren. Dann gibt es die Option, dass die Sprache interpretiert wird. Und da gibt es verschiedenste Mischformen davon, die zum Beispiel in Virtual Machines oder mithilfe von Just-in-Time-Compilation implementiert sind. Was das alles genau bedeutet, schauen wir uns jetzt ein bisschen genauer an, indem ich einfach ein paar Beispiele dazu erkläre oder beziehungsweise das ganze schematisch einfach mal beschreibe. Und zwar fange ich da an mit dem Kompilieren. Was man da wie im Prinzip bei allen Arten, der Ausführungen gegeben hat, ist im Anfang unser Programm in dieser Quellsprache, also in der Programmiersprache, in der der Entwickler das Programm aufschreibt. Und im Falle vom Kompilieren wird dieses Programm dann an einen Tool namens Compiler gegeben. Und was dieser Compiler macht, ist das Programm jetzt zu übersetzen in das Zielprogramm in der Zielsprache, was zum Beispiel die Sprache sein kann, die die Hardware versteht, auf der das Programm schlussendlich laufen soll. Und wenn wir das Programm jetzt ausführen, dann wie bei jedem Programm gibt es Eingaben und Ausgaben und die werden jetzt direkt in dieses Zielprogramm gegeben, was als Ergebnis dann die entsprechenden Ausgaben produziert. Das heißt, der Compiler spielt während der Ausführung dann keine Rolle mehr, sondern die Eingaben gehen direkt ins Zielprogramm und landen anschließend wieder beim Benutzer. Alternativ dazu gibt es die Möglichkeit, dass Programme auch interpretiert werden können. Und wie das aussieht, schauen wir uns jetzt mal an. Also hier geht es genau wie gerade eben wieder los mit einem Programm in der Quellsprache. Plus werden wir das jetzt nicht Kompilieren, sondern wir geben dieses Programm an ein anderes Tool, nämlich den Interpreter. Und was der Interpreter macht, ist eine Instruktion nach der anderen in diesem Quellprogramm anschauen und direkt ausführen. Das heißt, er übersetzt nichts, sondern führt diese Instruktion direkt aus, indem er sie sozusagen interpretiert. Und das heißt, die Eingaben, die wir an unser Programm jetzt geben wollen, werden dann auch direkt in diesen Interpreter reingegeben und er produziert dann entsprechende Ausgaben, indem er schaut, was die Instruktionen im Programm mit diesen Eingaben denn so anstellen. Also das funktioniert ganz anders als in dem Kompilierungsmodell, was wir vorher gesehen haben, weil eben die Eingaben und Ausgaben direkt aus dem Interpreter kommen. Also das Werkzeug, was uns hilft, das Programm auszuführen, ist während der Ausführung auch noch dabei. Die dritte Variante ist im Prinzip so eine Kombination aus dem, was wir schon vorher gesehen haben. Und das ist, was bei Just In Time Compilation passiert. Just In Time Compilation wird zum Beispiel angewandt für Java oder auch für JavaScript. Was ich jetzt auch mal, ist mehr an dem Beispiel von Java orientiert. Was da passiert, ist, dass wir wieder unser Quellprogramm haben, also in dem Fall eine Java Datei zum Beispiel, die geben wir einem Compiler. Für Java heißt der Java C. Und die meisten hier haben den vermutlich auch schon mal irgendwann ausgeführt. Und was dieser Compiler jetzt produziert, ist eine Form von Bitcode, zum Beispiel Java Bitcode, im Falle von Java, die jetzt aber an sich noch nicht ausgeführt werden kann, weil das nur eine Zwischensprache ist, die dazu dient, dieses Programm in die eigentliche ausführende Einheit, nämlich die virtuelle Maschine reinzugeben. Und diese virtuelle Maschine besteht nun wiederum aus zwei Teilen. Zum einen haben wir einen Interpreter, der diesen Bitcode nimmt und sofort interpretieren kann. Der Vorteil von diesem Interpreter ist, dass keine weiteren Schritte nötig sind, sondern man in Prinzip sofort loslegen kann, das Programm auszuführen, indem man es interpretiert. Und gleichzeitig gibt es in dieser virtuellen Maschine aber auch noch einen Just In Time oder JIT Compiler, der genau für die Teile des Programms aufgerufen wird, die etwas länger brauchen oder die häufiger ausgeführt werden. Und wo Sinn machen würde, diese Teile des Programms zu optimieren und schnelleren Code zu generieren, der dann diese Teile des Programms tatsächlich direkt auf der physischen Maschine ausführen kann. Also dieses ganze Ding hier zusammen, der Bitcode Interpreter und der JIT Compiler, die miteinander interagieren, nennt sich dann virtuelle Maschine. Und was jetzt noch fehlt in dem Bild, sind die Eingaben und Ausgaben, die wir in das Programm reingeben wollen und aus dem Programm wieder rausgeben. Und die werden so ähnlich wie bei dem Modell, was wir vorher hatten, wo wir nur den Interpreter hatten, direkt in die virtuelle Maschine reingeben, wo dann die eigentliche Ausführung stattfindet und schlussendlich kommen die Ausgaben da auch wieder raus. Wenn man sich diese verschiedenen Optionen, so eine Sprache zu implementieren, jetzt anschaut und ein bisschen darüber nachdenkt, warum bestimmte Sprachen vielleicht so oder so implementiert sind, dann stellt man fest, dass bestimmte Sprachfeatures einfacher auf bestimmte Arten und Weisen zu implementieren sind als vielleicht andere. Also ein konkretes Beispiel ist vielleicht dynamisch generierter Code, also Runtime Code Generation. Das heißt, ich habe irgendwo in meinem Programm ein Mechanismus, der einen String nimmt und in diesem String steht Programmcode und zur Laufzeit kann ich diesen Code dann tatsächlich als ein Programm interpretieren und auch noch wieder ausführen. Das ist ein Feature, was relativ viele Sprachen zur Verfügung stellen. Also zum Beispiel in JavaScript gibt es da diese Ival-API, der ich einfach einen String gebe und dieser String wird dann tatsächlich ausgeführt als Code und das hat den Vorteil, dass ich zum Beispiel zur Laufzeit einen neuen Code von irgendeinem Server laden kann und den dann sofort ausführen kann, ohne mein Programm jetzt neu starten zu müssen. Das Problem ist, dass dieser Code ja nicht zur Compile Time, also zum eigentlichen Zeitpunkt, wo ein Compiler einsetzen wird, bekannt ist. Das heißt, es ist unmöglich, diesen Code vorher zu kompilieren, und es ist sehr, sehr einfach, diesen dynamisch generierten Code zu interpretieren, weil der Interpreter muss den Code ja nicht vorher sehen, sondern er kriegt den und fängt sofort an, den auszuführen. Und so ähnlich ist es auch bei vielen anderen Sprachfeatures, dass die manchmal einfacher zu interpretieren sind, manchmal einfacher zu kompilieren sind und just in time compilation, weil es eben so zwischen diesen beiden Modellen steht, ist da oft ein guter Kompromiss, weil man nämlich die Sachen, die man vorher kompilieren kann, vorher kompiliert und die Sachen, die man erst zur Laufzeit kennt und vielleicht auch erst zur Laufzeit richtig optimieren kann, dann eben erst zur Laufzeit optimiert. Neben diesen Werkzeugen, die wir gerade betrachtet haben, also Compiler, Interpreter und Virtual Machines, gibt es noch eine ganze andere Reihe von Tools, die häufig im Zusammenhang mit Programmiersprachen verwendet werden. Und ich möchte jetzt einfach nochmal einen kleinen Überblick über diese drei, die hier aufgezählt sind, also linker Pre-Processors und Source-to-Source-Compilers gehen, damit jeder bekommt, was die eigentlich genau machen. Fangen wir mal mit dem Linking an. Also linker ist ein Tool, was insbesondere zum Tragen kommt in Sprachen, die klassischerweise kompiliert werden, also zum Beispiel C. Wie immer haben wir da irgendwo unser Quellprogramm und weil das ja für kompilierte Sprachen benutzt wird, geben wir das zunächst mal in den Compiler. Und der Anfang vom Bild sieht jetzt so ähnlich aus wie das, was ich vorhin für Kompilierung gemalt habe, nämlich jetzt kommt hier unser Target-Programm raus. Und das Problem ist jetzt eigentlich, dass die Programme, die der Programmierer schreibt, natürlich noch nicht vollständig sind. Fast niemand schreibt denn ein Programm, wo wirklich alles drin ist, was zu dem Programm gehört. Sondern was man standardmäßig macht, ist natürlich auch noch Bibliotheken zu verwenden, in denen häufig wiederkehrende Probleme einfach schon gelöst werden, so was wie Ausgaben auf die Konsole schreiben oder vielleicht bestimmte mathematische Operationen ausführen oder mit dem Netzwerk interagieren. Und all das will man natürlich nicht jedes Mal neu erfinden, sondern man möchte Bibliotheken verwenden, die das Ganze schon implementieren. Und die Frage ist jetzt, wie kann dieses Programm, was der Compiler generiert und diese Bibliotheken, die vielleicht in der Regel auch vorher von einem Compiler kompiliert wurden, verknüpft werden. Und das ist genau das, was der Linker macht. Also der Linker nimmt dieses bisher noch unvollständige Target-Programm und diese Bibliothek, die an sich auch kein vollständiges Programm ist, weil sie einfach nur APIs zur Verfügung stellt, die man aufrufen kann. Und verknüpft die dann oder linkt die dann in ein vollständiges Programm, was dann tatsächlich auch auf der Hardware ausgeführt werden kann. So, das wäre jetzt ein Beispiel von einem weiteren Tool, was häufig verwendet wird. Als weiteres Beispiel möchte ich mir noch die Pre-Processes oder Pre-Prozessoren anschauen. Was die machen ist, das Quellprogramm zu nehmen und vorzuverarbeiten, bevor das Programm dann dem eigentlichen Compiler gegeben wird. Also wir haben in dem Fall wieder unser Source-Programm, könnte zum Beispiel wieder ein C-Programm sein. Und weil C-Programme manchmal Dinge enthalten, die so eigentlich gar nicht so richtig zur Sprache gehören und vor allem vom Compiler noch nicht verstanden werden, gibt es dann erstmal den Pre-Processor. Und was der macht, ist bestimmte Änderungen am Programm vornehmen, sodass wir dann eine modifizierte Version dieses Source-Programms bekommen, was aber immer noch in der selben Programmiersprache geschrieben ist. Also wenn das Source-Programm in C war, dann ist das modifizierte Source-Programm eben auch noch in C. Und zum Beispiel könnte dieser Pre-Prozessor Macros expandieren. Und Macros sind im Prinzip einfach ein Mechanismus in C, um bestimmte Transformationen, die im Programm stattfinden sollen, bevor das Programm kompliziert wird, auszudrücken. Und nachdem das Ganze dann passiert ist, kann dieses modifizierte C-Programm oder Source-Programm zum Compiler gegeben werden. Und was der dann macht, ist das, was er vorher auch schon gemacht hat, nämlich einfach das Zielprogramm entsprechend auszugeben. So und als letztes Werkzeug, was auch häufig zum Einsatz kommt, schauen wir uns mal noch die Source-Source-Compiler an. Wie der Name schon sagt, ist das auch wieder ein Compiler. Allerdings einer, der eben nicht das Quellprogramm in die Zielsprache übersetzt, sondern das Quellprogramm in eine andere Quellsprache übersetzt. Und es gibt verschiedene Gründe, warum man das gern macht. Ein Grund ist, dass es vielleicht sehr gute Compiler für eine bestimmte Sprache schon gibt. Und ich jetzt nicht für die neue Sprache dieses komplette Ökosystem neu erfinden möchte und neue Compiler schreiben möchte, sondern vielleicht einfach zu dieser Sätze, so dass ich die exzitierten Compiler für diese Sprache schon wieder verwenden kann. Also wenn ich zum Beispiel mein Source-Programm in einer neuen Sprache habe, die vielleicht eine Erweiterung von C ist, dann könnte ich mein Source-to-Source-Compiler ich nenne den mal Compiler1 hier aufrufen. Und was der dann macht, ist dieses Programm einfach in eine andere Sprache, zum Beispiel in normales C, zu übersetzen. Und weil ich das Programm jetzt in C habe und das semantisch genau das gleiche macht, wie das ursprüngliche Quellprogramm, was ich reingegeben habe, kann ich jetzt hier den existierenden C-Compiler oder irgendein anderen zweiten Compiler aufrufen. Und was der dann macht, wenn das ein normaler Compiler, also kein Source-to-Source-Compiler ist, ist, dass er uns das Zielprogramm wieder generiert, zum Beispiel dann in der Maschinensprache für die Maschine, wo das Ganze schlussendlich ausgeführt werden soll. So, jetzt haben Sie einen kleinen Überblick bekommen darüber, was in dieser Veranstaltung Programmierparadigmen so alles dran kommen wird. Also wir werden uns verschiedene Paradigmen von Programmiersprachen anschauen, werden uns anschauen, wie Sprachen überhaupt spezifiziert werden können und natürlich auch wie Sprachen implementiert werden. Und damit sind wir auch am Ende dieser ersten Einheit oder dieses ersten Blockis und der dritten Einheit innerhalb dieses ersten Blockis. Und ich bedanke mich fürs Zuhören und bis zum nächsten Mal.