 Ja, herzlich willkommen zurück zu Programmierparadigmen. Wir sind hier in Teil 5 des Moduls Names, Scopes and Bindings. Und worum es hier gehen soll, ist folgendes, na genau, nämlich Referencing Environments oder Referenzumgebung. Und was das genau ist und warum das in Programmiersprachen überhaupt eine Rolle spielt, sehen wir gleich. Fangen wir vielleicht mal an mit dem Begriff überhaupt Referencing Environment. Was ist das eigentlich? Was man damit meint, ist die Menge aller Bindings, die an einem bestimmten Punkt in der Programmausführung oder an einem bestimmten Punkt im Programm existieren. Was genau diese Bindings sind, das wird von den Scoping-Regeln, die wir ja bereits gesehen haben, definiert. Also das kann zum Beispiel Static oder Dynamic Scoping sein. Aber wie auch immer diese Regeln aussehen, an jedem Punkt im Programm gibt es eben diese Bindings, die genau da aktiv sind. Und diese komplette Menge an Bindings, die bezeichnet man als Referencing Environment. Warum ist das überhaupt interessant? Tja, das ist dann interessant, wenn wir eine Referenz auf eine Funktion erzeugen. Also sprich, wenn wir irgendwo eine Funktion haben und die dann in eine Variable schreiben und die dann zum Beispiel woanders hin weitergeben, sodass die Funktion später aufgerufen werden kann. Denn die Frage ist dann, welche Scoping-Regeln oder welche Bindings existieren denn dann, werden die Scoping-Regeln dann angewandt, wenn die Funktion aufgerufen wird oder werden die Scoping-Regeln dann angewandt, wenn diese Referenz auf die Funktion erstellt wurde. Und genau da unterscheiden sich halt einige Sprachen voneinander. Und genau das ist es, was wir jetzt in diesem fünften und letzten Teil des Moduls ein bisschen genauer anschauen wollen. Um das Ganze ein bisschen konkreter zu machen, schauen wir uns einfach mal ein Beispiel an. Und zwar wieder in einer Pseugeprogrammiersprache, sodass wir dann verschiedene Arten und Weisen, wie diese Frage beantwortet werden kann, am selben Beispiel illustrieren können. Was wir hier sehen, ist, dass eine Funktion B existiert und wir die Referenz oder eine Referenz auf diese Funktion B als Rückgabewert von A zurückgeben. Das heißt, A wird hier dann aufgerufen und das, was A zurückgibt, ist eine Referenz auf eben diese Funktion B, die dann hier unten zufällig auch wieder B heißt, wobei das natürlich im Allgemeinen nicht so sein muss. Und die Frage ist jetzt, was passiert, wenn wir diese Funktion aufrufen oder noch genauer? Was für Bindings existieren denn dann, wenn diese Funktion schlussendlich ausgeführt wird? Was die Funktion macht, ist, dass sie X ausgibt. Das heißt, konkret ist für dieses Beispiel die Frage, an welches Speicherobjekt dieser Name X denn gebunden ist und was wird dann schlussendlich ausgegeben, wenn wir diesen Code ausführen? Wie man sieht, gibt es ja eine ganze Reihe von Definition von X. Also es gibt hier oben die, wo X23 wird oder hier unten die, wo X42 wird und dann hier nochmal eine, wo X5 wird. Und die Frage ist, welche dieser vielen Definition von X ist denn dann die, die hier oben gelesen wird? Und das hängt eben davon ab, wie genau das Referencing-Environment für diese Funktion B erstellt wird. Eine mögliche Antwort auf diese Frage ist der sogenannte Shallow Binding und das ist häufig in Programmiersprachen zu finden, die Dynamics Scoping verwenden. Die Grundtidee hier ist, dass das Referencing-Environment, also die Menge aller Bindings innerhalb dieser Funktion dann erstellt wird, wenn die Funktion aufgerufen wird. Gehen wir mal zurück zu unserem Beispiel von gerade eben, was wir hier unten wieder sehen. Also wir haben jetzt hier diesen Aufruf von B und die Frage war, welchen Wert wird denn dieses X dann haben, was wir hier in B lesen? Und wenn wir jetzt so eine Sprache haben, die Shallow Binding verwendet, dann wird X den Wert haben, den diese globale Variable X hier hat. Denn wir erstellen das Referencing-Environment, also wir erstellen das Binding von X erst in dem Moment, wo B aufgerufen wird. Das heißt, wir schauen hier in B selbst nach, ob da irgendwo eine Variable X definiert ist. Und wenn das nicht der Fall ist, schauen wir dann in den Scope, wo B aufgerufen wurde. Und was wir da eben sehen, ist hier in diesem globalen Scope, dass es diese Variable X mit dem Wert 5 gibt. Das heißt, der Code würde in dem Fall 5 ausgeben, weil wir Shallow Binding verwenden. Die Alternative zum Shallow Binding nennt sich Deep Binding. Und diese Form zu definieren, was dann in diesem Referencing-Environment genau drin ist, findet man üblicherweise in Sprachen, die auch Static Scoping verwenden. Die Grundidee hier ist, dass das Referencing-Environment genau dann festgelegt wird, wenn die Referenz zu der Funktion, für die das Ganze erstellt wird, auch erstellt wird. Also in dem Moment, wo die Referenz zur Funktion erstellt wird, wird auch das Referencing-Environment zusammengebaut. In unserem Fall heißt das folgendes. Die Funktion an sich existierte ab dem Moment, wo die hier definiert ist. Aber die Referenz zur Funktion, die erstellen wir hier, wenn wir nämlich diese Funktion in der Form von B zurückgeben. Das heißt, hier in dem Moment wird dann das Referencing-Environment aufgebaut. Das heißt, hier in dem Moment schauen wir nach, für jeden Namen, der in der Funktion B verwendet wird, also in dem Moment z.B. für X, schauen wir nach, wie wir das X auflösen können und tun das in dem aktuellen Stück Code, wo wir sind. Das heißt, das X wird hier an die lokale Variable innerhalb von A gebunden, die X heißt, sprich die, die hier oben definiert wird und die, die hier unten definiert wird. Und da der aktuellste Wert von X in dem Moment, wo wir dann die Methode aufrufen, der ist, der hier als Zweites geschrieben wird, nämlich das 42, gibt schlussendlich dieses X hier diesen Wert 42 aus. Also das ist anders als bei dem Shallow Binding, was wir gerade eben gesehen haben, weil ganz einfach das Referencing-Environment zu einem anderen Zeitpunkt erstellt wird, nämlich dann, wenn die Referenz auf die Funktion erstellt wird und das heißt, lange bevor oder zumindest bevor die Funktion schlussendlich aufgerufen wird. Der Begriff, den vielleicht der eine oder andere in dem Zusammenhang schon mal gehört hat, ist der eine Closure. Und was das nämlich genau ist, ist, dass eine Closure im Prinzip einfach nur eine Implementierung dieser Idee des Deep Binding ist. Also konkret besteht eine Closure eigentlich aus zwei Sachen, nämlich einmal eine Repräsentation des Referencing-Environments, also diese Informationen, welche Bindings von Namen zu Speicherobjekten denn existieren, plus der eigentlichen Funktion, die Teil dieser Closure ist. Das heißt, wenn ich eine Referenz auf eine Funktion erstelle, dann wird in Sprachen die Deep Binding verwenden, eine Closure erstellt und das ist nämlich genau dieser Referenz auf die Funktion, plus eben das Referencing-Environment. Das heißt, die Namen werden in dem Moment gebunden, wo diese Referenz zu der Funktion dann auch erstellt wird. Diese Idee von Closures existiert mittlerweile in ziemlich vielen Sprachen, eigentlich quasi in jeder Sprache, in der man Funktion oder Referenzen auf Funktion in Form von Variablen weitergeben kann. Und eine Sprache, wo das relativ häufig verwendet wird, ist JavaScript. Wir haben hier mal ein Beispiel von ein bisschen JavaScript Code, wo mehrere Closures erstellt werden. Das Stück Code ist etwas künstlich, in dem Sinne, dass man normalerweise etwas weniger komplizierte Sachen mit den Closures macht, aber das ist einfach, um dieses Konzept in seiner ganzen Schönheit mal darstellen zu können. Was der Code hier macht, ist, dass wir diese Funktion namens Auto aufrufen, die ist hier oben definiert und was die als Argumente erwartet, ist einmal eine Zahl und einmal eine Funktion. Also diese Funktion ist eine Referenz auf eine andere Funktion, die dann irgendwie aufgerufen werden kann. Innerhalb von Auto gibt es zwei Fälle, nämlich den einen, wo wir diese Funktion, die übergeben wird, aufrufen und den anderen, wo wir Auto selbst nochmal aufrufen, und zwar mit derselben Zahl K, die wir gerade bekommen haben, aber diesmal plus eins. Und wo wir als die Funktion, die dann vielleicht später aufgerufen werden kann, Inner mitgeben und Inner ist eine Funktion, die hier innerhalb von Auto nochmal definiert ist. Und was dieses Inner macht, ist den Wert von K auszugeben und eventuell mehrmals in diesem Beispiel und wie genau, schauen wir uns jetzt gleich mal an. So, um mal zu zeigen, was da zur Laufzeit dieses Programmes eigentlich passiert, mal ich mal sowas ähnliches wie einen Callstack auf und zwar geht der los mit unserer Methode Main, die hier in dem Fall nicht explizit zu sehen ist, sondern einfach, dass das gesamte Skript ist, was ich mal als Main bezeichne. Main ruft Auto auf, das heißt, ich füge hier mal so ein Allocation Frame hinzu, der gehört jetzt also zu Auto und was ich in den Allocation Frame reinschreibe, als einzige Information, sind jeweils die Werte der Argumente, die diese Funktion übergeben werden, was nicht ganz genau das ist, was normalerweise im Allocation Frame steht, aber die Idee wird, glaube ich, trotzdem klar und zwar bekommt dieser Aufruf von Auto K mit minus 1 übergeben und außerdem diese Referenz auf die Funktion namens Other. Jedes Mal, wenn so eine Referenz auf eine Funktion erstellt wird, wird ein Referencing Environment dargestellt und das zeige ich einfach mal durch so gestrichelte Pfeile und das Referencing Environment für diese Funktion Other, die hier erstellt wird, umfasst all das, was in dem Moment, also die Bindings, die in dem Moment hier in Main existieren, sprich, das Referencing Environment von diesem Other ist eben dieses grün gestrichelte, diese Bindings, die in diesem grün gestrichelten V-Eck drin sind und die Clojure besteht dann sozusagen aus diesem grün Referencing Environment plus der Implementierung von Other selbst, die in dem Fall allerdings nichts enthält. So, dann haben wir in Outer, jetzt also minus 1 und diese Funktion übergeben. Das heißt, wir schauen das if an, schauen ob das k größer als 0 ist, ist nicht der Fall. Das heißt, wir rufen Outer nochmal auf, sprich, wir haben hier einen weiteren Aufruf, wo k jetzt den Wert vom k von vorhin plus 1, also sprich, 0, bekommt und wo das Argument diese Funktion jetzt auf inner zeigt, und inner ist ja diese Funktion, die innerhalb von Outer definiert ist, weil wir da wieder eine Referenz auf eine Funktion übergeben und die hier in dem Moment erstellen, gibt es ein weiteres Referencing Environment, nämlich das, was ich jetzt hier mal durch so rot gestrichelt darstelle und das wird in dem Moment erstellt, wo wir in Outer sind, sprich, das enthält all die Bindings, die hier innerhalb dieses Kastens aktiv sind. Also dieses inner ist eine Clojure, die aus der Implementierung von inner besteht und dem roten Referencing Environment. So, dann sind wir also jetzt wieder in Outer drin und haben jetzt wieder diese Überprüfung, ob k größer als 0 ist, k ist 0, 0 ist nicht größer als 0, das heißt, wir rufen zum dritten Mal Outer auf, k hat jetzt den Wert vom alten k plus 1, also ist jetzt 1 und die Funktion, die wir hier übergeben, ist wieder eine Clojure, die wieder als Code die Implementierung von inner enthält, jetzt aber ein anderes Referencing Environment, was ich jetzt hier mal durch diese lila pink gestrichelte Linie darstelle und zwar wird das ja entweder in dem Moment erstellt, wo die Referenz auf inner erstellt wird, sprich, wir haben alle Bindings in diesem Referencing Environment, die hier existieren, in diesem lila gestrichelten Viereck, sprich, die Clojure zeigt eben auf dieses Referencing Environment. So, dann sind wir jetzt in der dritten Ausführung von Outer, Outer schaut wieder, ob k größer als 0 ist und diesmal ist k 1, das ist größer als 0, sprich, diesmal geben wir in den if 2 rein und rufen dann also inner auf, was ich jetzt mal noch hier oben drauf schreibe, inner nimmt keine Argumente, also muss ich da auch keine eintragen und jetzt ist die Frage, was in inner eigentlich passiert und welcher Wert von k jetzt schlussendlich hier ausgegeben wird. Was ich jetzt der Vollständigkeit halber auch mal noch eintrag, ist der Static Link, den wir ja vorhin schon besprochen hatten, denn der zeigt immer auf den syntaktisch umgebenden Scope, also in dem Fall würde der auf den Scope von Outer zeigen, sprich, das blaue hier ist unser Static Link. Und im Gegensatz dazu sind diese gestrichelten Pfeile, die ich hier aufgemalt habe, alle Referencing Environments, wie sie eben in den drei Closures, die hier erstellt werden, gespeichert werden. So, und die Frage ist jetzt, was gibt das inner an der Stelle aus? Die inner Funktion, die wir hier aufrufen und sprich der der Closure, die in dem obersten Outer erstellt wurde, sprich die Closure, die durch diese lila, eine gestrichelte Linie dargestellt wird. Das heißt, was der Code machen wird, er schaut in dem Referencing Environment, was lila ist, nach und schaut da, ob k definiert ist und sieht dann als erstes das k in dem obersten Outer und das bedeutet, dass wir schlussendlich 0 an der Stelle ausgeben, denn dieses k, was dann hier definiert ist, das wird schlussendlich gelesen. Und was eben wichtig ist, ist, dass wir nicht dem Static Link folgen. Wenn wir das getan hätten, wären wir nämlich bei dem Wert minus 1 rausgekommen. Aber weil wir hier eben eine Referenz auf eine Funktion übergeben und das eine Closure erstellt und diese Closure ein Referencing Environment enthält, folgen wir diesem und schauen eben entsprechend dem Referencing Environment nach, auf welches Objekt dieser Name k denn zeigt und das ist eben der Wert von k, der 0 enthält. Wer das Beispiel jetzt vielleicht ein bisschen verwirrend fand, dem würde ich empfehlen, das einfach nochmal selber in JavaScript auszuprobieren und vielleicht auch ein bisschen Sachen zu verändern, um ein besseres Gefühl für diese Closures zu bekommen und sich vielleicht auch nochmal selber für eine Variante dieses Beispiels so ein ähnliches Bild aufzumalen, wie ich das da gerade gemacht habe. Zum Abschluss haben wir hier noch ein kleines Quiz, wo ich vier Sätze aufgeschrieben habe, von denen manche stimmen und manche auch nicht stimmen. Alles ist zum Thema Scopes und Bindings und Namen in Programmiersprachen. Und einfach zum Überprüfen, was so hängen geblieben ist, würde ich Sie bitten, einfach für diese vier Sätze jetzt mal in Milias abzustimmen, welche dieser Sätze denn korrekt sind und welche dieser Sätze falsch sind. Und wenn Sie das gemacht haben, dann weiterzuschauen. Schauen wir mal die Lösung an. Also die ersten zwei Sätze sind korrekt. Scoping-Regeln, z.B. statisch oder dynamisches Scoping, legen eben genau fest, welche Namen wie an Speicherobjekte gebunden werden. Das stimmt. Der zweite Satz stimmt auch, nämlich das Bild in Objects so ein unsichtbares, implizites Scope erstellen, was außerhalb von den anderen Scopes, die es gibt, immer noch auch existiert. Der dritte Satz stimmt nicht. Sprachen, die Punkte haben, können natürlich Aliasing haben und das C-Beispiel, was wir vorher schon gesehen haben, ist eben genau ein Beispiel dafür. Und dann schlussendlich die Frage der vierte Satz Closures implementieren Referencing-Environments. Das stimmt, aber eben nicht die, die durch Shallow Binding erstellt werden, sondern die, die durch Deep Binding erstellt werden. Ja, und damit sind wir schon am Ende dieses Moduls zum Thema Names, Scopes und Bindings im Programmiersprachen. Sie wissen jetzt also, was Namen überhaupt im Programmiersprachen sind, wozu die gut sind und wie eigentlich aufgelöst wird, was so ein Name bedeutet, also auf welches Objekte im Speicher ein bestimmter Name zu einem bestimmten Zeitpunkt im Programm überhaupt zeigt. In verschiedenen Programmiersprachen haben verschiedene Scoping-Regeln oder verschiedene Regeln, wie das Referencing-Environment erstellt wird. Und es ist immer wichtig, dass man die Regeln der Programmiersprache gut kennt, die man gerade benutzt und natürlich auch ein bisschen allgemeines Verständnis dafür hat, was für Regeln es überhaupt gibt, sodass man zukünftige Sprachen dann auch entsprechend leichter versteht. Und damit erstmal vielen Dank fürs Zuhören und bis zum nächsten Mal.