Ich möchte Sie heute hoffentlich gleich zusammen mit Fred Winkler
zur vorletzten Vorlesung in diesem Semester.
Und für Sie zur vorletzten Vorlesung vom SE-Zyklus begrüßen.
Heute habe ich mir ein Thema vorgenommen oder haben wir uns
ein Thema vorgenommen, was eher auf der theoretischen Seite liegt.
Aber ich hoffe, Ihnen klarmachen zu können,
dass das jetzt nicht nur Theorie aus dem Elfenbeinturm ist,
sondern dass das ein Thema ist,
zu dem jeder ambitionierte Softwareentwickler oder jeder ambitionierte Softwareentwicklerin
eine Position haben sollte
Es geht aus der Theorie betrachtet um die Formalisierung von Programmiersprachen.
Also ein Thema, was große Teile der theoretischen Informatik,
kann man sagen, über 30 Jahre beschäftigt hat.
Eigentlich nur mehr als 30 Jahre. Aus dem Bereich der
formalen Ansätze werde ich einen Bereich herausnehmen,
den wir aus der Sicht der Softwaretechnik in den Vordergrund stellen wollen,
nämlich die abstrakten Datentypen,
abgekürzt ADT
Und nach, ich könnte fast sagen,
um in der Metapher zu bleiben,
nach der Halbzeitpause wird Fred ihnen dann mal zeigen,
dass das, was ich ihnen hier vorne konzeptionell erzähle,
auch ganz spannend in der Programmierung umgesetzt werden kann.
Er wird nämlich einen Stack bauen, der den Grundprinzipien der abstrakten Datentypen entspricht.
Okay, über was rede ich heute? Ich rede um
die Ansätze über die Ansätze, die Zuverlässigkeit von Programmen zu erhöhen.
Und zuverlässig heißt also, dass ein Programm auch das macht,
was es machen soll oder was festgelegt worden ist,
was es machen soll. Und wie man da rankommen kann,
das ist die zentrale Idee heute und im zweiten Teil wollen wir dann zeigen,
wie man das eventuell dann auch so mal machen kann.
Natürlich gibt es Literatur dazu, es gibt jede Menge Literatur
dazu, aber Wir haben hier drei Arbeiten ausgewählt.
Zuverlässigkeit nach Bertromeyer besteht aus zwei Facetten, nämlich einmal Korrektheit
und zum zweiten Robustheit. Korrektheit ist also im engeren Sinne das,
die Fähigkeit von Software, die Aufgaben zu erfüllen,
die in einer Vorgabe für die Software festgelegt worden sind.
Ich betone das deshalb, weil es weniger um die Frage geht,
über die wir hier auch schon einige Male gesprochen haben.
Also es geht weniger um die Frage, können unsere Anwender und Benutzer
gut mit der Software umgehen, liegt sie gut in der Hand.
Sondern es geht wirklich um den Kern,
um den semantischen Kern der Software, tut sie das,
was wir vorher festgelegt haben, was sie formal tun soll.
Und können wir das in irgendeiner Weise stärker zusichern,
stärker festlegen, als nur zu sagen,
ich habe hier auf einem Blatt Papier den Algorithmus hingeschrieben,
der von dieser Software berechnet werden soll.
Gibt es mehr Möglichkeiten? Den zweiten Teil,
der auch dazugehört, den werde ich heute nicht behandeln,
nämlich Robustheit, also läuft die Software auch erwartungskonform weiter,
wenn wir statt 2014 in Ziffern 2014 als Text eingeben über die Tastatur oder passiert dann irgendetwas Merkwürdiges,
was für keinen nachvollziehbar ist oder sogar irgendetwas Schädliches.
Also ich befasse mich jetzt erstmal mit der Frage,
wir wollen korrekte Software,
denn wenn die Software in ihrem semantischen Kern nicht das tut,
was sie tun soll,
dann brauchen wir sie auch
gar nicht auszuführen
Es geht also eigentlich immer darum, dass ich schriftlich,
möglichst schriftlich, zumindest zweifelsfrei festlege,
was ich im Kern von der Software an Semantik erwarte und wie ich festlegen kann,
dass das, was an Programmcode da steht, dies auch tatsächlich tut.
Alle Ansätze, die bis jetzt in der theoretischen Informatik über
Jahrzehnte verfolgt worden sind, gehen immer davon aus,
dass sich diese formalen Beweise Mehr minder mit mathematischen Mitteln machen kann.
Und wir kommen in Betrachtung von 50 Jahren formalen Ansätzen zu einem Ergebnis.
Das ist extrem schwierig. Unser Ergebnis vorweggenommen heißt für
große und interaktive Programme ist der formale Beweis,
dass ein ganzes Programm das tut,
was ich an Semantik für dieses Programm festlegen kann,
dieser Beweis ist heute eigentlich nicht machbar
Damit wäre ich an sich fertig, wenn wir das hier belegen könnten und sagen,
es kommt auf Korrektheit an, aber wir können es nicht machen.
Das hilft uns aber nicht. Das hilft uns nicht als Softwaretechniker,
Softwaretechnikerin, können wir nicht sagen, naja,
man kann aufschreiben, was man will,
ob das tatsächlich in der Software dann hinterher so abläuft,
wissen wir einfach nicht.
Gut, hier nochmal im Kern, es geht um das,
was die Spezifikation der Semantik oder Spezifikation der formalen Semantik
genannt wird
Sie haben bis jetzt ja sehr viel über objektorientierte Programmierung feststellen können.
Und sie haben gesehen, auch in den ganzen Ansätzen,
die Axel Schmolitzki mal vorgetragen hat, ganz wichtig ist,
dass wir nach dem Kapselungsprinzip an Klassen das sehen,
was sie nach außen zeigen und dass wir uns nicht vorrangig mit dem Programmcode im Inneren beschäftigen,
also eine Klasse soll deutlich machen an ihrer Schnittstelle,
was sie an Dienstleistungen an Operationen anbietet.
Das ist die grundlegende Idee, aber sie wissen auch,
dass die Schnittstelle im Grunde genommen nur die Signaturen der Operationen anbietet.
Also die Namen und die Parameter und ihre Typisierung.
Mehr können wir an der Schnittstelle nicht festlegen und das ist verdammt wenig,
um zu sagen, anhand der Schnittstelle wissen wir,
was dieses Programm wirklich tut.
Da setzt eigentlich die ganze Diskussion um formale Semantik an.
Hier auch ein schönes Beispiel, wo Sie sich das nochmal klar machen können.
Wir haben hier drei Schnittstellen, Interfaces genannt Stack,
Interface Q und Interface Geblubber und wenn Sie sich die Namen ansehen,
dann ein bisschen Erfahrung, sagt Ihnen das was,
also Stack mit Push und Pop und ist MT.
Sie haben so eine Vorstellung, weil Sie schon mit Stacks gearbeitet haben,
was das ganze Ding soll, also Push und Pop sind die Operationen,
die etwas auf dem Stack drauf tun oder etwas vom Stack runterholen.
Schlangen oder Cues haben Sie vielleicht auch kennengelernt.
Man reiht ein Element in die Schlange rein oder man nimmt
es raus und Geblubber ist halt irgendwas.
Nur wenn ich mir jetzt mal jenseits der Namen
diese drei Interfaces ansehe,
dann sind die drei Interfaces gleich
Ja, von der Parametrisierung sind sie völlig gleich. Das heißt,
wenn mich jemand fragt, was unterscheidet ein Stack von einer Q und von irgendeiner Klasse die Geblubber,
implementiert, muss ich sagen, okay, sie hat die,
an der Schnittstelle haben die Operationen unterschiedliche Namen,
aber was die im Inneren tun, I don't know,
das sehe ich, diesen drei Interfaces nicht an.
Und das ist so, vielleicht so ein Hinweis,
wenn wir mehr aussagen wollen oder mehr festlegen wollen,
was hinter diesen Interfaces oder den sie implementierenden Klassen steht,
haben wir von der Programmiersprache her zunächst mal keine weitere Ausdrucksmöglichkeit,
um dann zu sehen, okay, dann sieh dir da bitte,
dann sieh dir doch bitte den Quellcode an,
ist ja genau das,
was wir nicht möchten.
Ja, hier steht noch, wir bemühen uns natürlich,
das in den Kommentaren hinzuschreiben, aber die Verbindung zwischen Kommentaren und dem,
was ein Programm macht, ist ja nun beliebig.
Sie können ja in 16 Punktschrift und Fett hinschreiben,
dieses Programm ist fehlerfrei,
An dem Code und was er beim Ablauf tut,
natürlich überhaupt nichts. Ich habe vorhin schon gesagt,
weil das als die große Schwäche der Programmierung schon ganz lange gesehen wird,
haben sich die Theoretiker bemüht, zu sagen,
gibt es unabhängig von dem Programmcode, der ein Programm konstituiert?
Gibt es unabhängig davon irgendetwas, um festzulegen, was die
Semantik eines Programms ist? Und dieser Nachweis soll halt,
wie ich es schon gesagt habe, mit mathematischen Methoden geführt werden und dann,
solange ich dem Mathematik vertrauen kann, kann ich sagen,
okay, dann soll das, was ich formal bewiesen habe,
auch jetzt im Programm tatsächlich ablaufen.
Es gibt im Grunde genommen drei verschiedene Ansätze. Ich will
auf diese formalen Ansätze nicht näher eingehen. Es kann sein,
dass sie die in der Theorie bekommen oder sich mal sonst aneignen.
Ich will das nur ganz kurz charakterisieren, um was geht es. Das eine ist,
zu sagen, was letztlich zählt, ist der Speicherzustand,
den ein Programm bearbeitet und das, was passiert,
wenn ich Operationen ausführe, wie verändert sich der Speicherzustand.
Und dieser erste Ansatz genannt die notationale Semantik,
gibt halt Regeln an, wie ich Schritt für Schritt
einen Speicherzustand durch Ausführen einer Programmiersprachlichen Operation in einen anderen Speicherzustand überführen kann.
Kein sehr simples Verfahren, auch nicht sehr einsichtig. Operationale Semantik,
das ist, wenn man sich mit Java so ein bisschen befasst hat,
so unter konzeptionellen Gesichtspunkten, ist schon etwas griffiger.
Da wird also gesagt, ich lasse mein Programm nicht auf
einem konkreten Rechner ablaufen und gucke, was rauskommt,
sondern ich bilde mir so einen virtuellen oder einen abstrakten Rechner.
Das heißt, ich habe so eine abstrakte Maschine,
die interpretiert mir mein Programm und die sagt mir in einer abstrakten Interpretation,
was rauskommt, wenn ich eine Operation mit Parametern rufe,
indem sie sie nicht wirklich ausführt oder einen Schritt für Schritt mit dieser abstrakten Maschine sagt,
was passiert,
wenn ich es aufrufe
Ich habe Java genannt, weil wir die Virtual Machine in Java ja schon mal haben,
das Java-Programm läuft ja nicht unmittelbar auf der zugrunde liegenden Hardware,
sondern läuft über diese virtuelle Maschine,
die wiederum dann die Hardware anspricht und die Basisoperation.
Also das Prinzip hat man heute schon auf Java übertragen. Deshalb gilt Java,
unter denen zurzeit eingesetzten Programmiersprachen als recht gutartig, weil die Fehler,
die in der Welt von CC plus plus immer aufgetreten sind,
bei Java nicht auftreten und diese Fehler waren,
ich lasse ein C-Programm auf einem Rechner X laufen das produziert
ein Ergebnis und dann lasse ich das gleiche Programm auf einem anderen Rechner vom Typ Y laufen und es kommt ein anderes Ergebnis raus, beispielsweise bei irgendwelchen numerischen Berechnungen.
Und jetzt muss ich erstmal unterscheiden,
liegt es daran, dass ich eine unterschiedliche Hardware,
eine unterschiedliche CPU benutzt habe oder was hat sonst diesen Unterschied produziert?
Wenn ich also so eine virtuelle Maschine habe,
dann bin ich mir relativ sicher,
dass auf welchem Rechner ich auch immer ein Java-Programm ausführe,
plus minus Feinheiten,
im Endeffekt auch bei numerischen Berechnungen
das Gleiche rauskommt
So, der dritte Ansatz, die sogenannte axiomatische Semantik,
hat ganz lange in der Programmierausbildung eine große Rolle gespielt.
Also wenn Sie hier vor, sagen wir mal, 20 Jahren gesessen hätten,
oder Sie sitzen 200 plus Kilometer entfernt in einer anderen Universität und machen dort Programmierausbildung,
werden Sie das sogenannte Horkalkül kennen gelernt haben, hätten Sie kennengelernt.
Das ist also ein Ansatz, so mit Vor- und Nachbedingungen zu sagen,
was sind die Vorbedingungen, um eine Operation auszuführen?
Und wenn ich Sie dann ausführe, innerhalb der Logik,
welche Nachbedingungen müssen dann bilden. Das ist das sogenannte Horrkalkül,
hat eine große Bedeutung in der Programmierausbildung gehabt,
also üblicherweise in Übungen macht man dann sowas,
dann kriegt man so, das weiß ich, zehn Zahlen,
Java-Code und dann müssen sie sich dazu die Vorbedingungen,
die Nachbedingungen rausrechnen und müssen dann also sagen,
wenn ich dieses Programm mit 15 und 23 als Integer-Eingabe parat mit der Rufe,
was kommt im Endeffekt,
was ist die Bedingung,
die nach Ablauf dieses Programms
gelten muss
Das lässt sich schon nicht so einfach erklären,
das ist noch schwerer in der Praxis, so zu Fuß umzusetzen,
sehr aufwendiges Verfahren, aber immerhin ein praxisorientiertes Verfahren,
weil es dieses Horkalkül für halt herkömmliche Programmiersprachen gibt.
So, aber auch das Horkalkül trägt eigentlich nur so in
einer Welt, wo ich als Parameter richtige Werte habe,
also so Intager oder auch mal eine Zeichenkette.
Dann funktioniert das ganz gut, also die klassischen inperativen Programme,
die lassen sich mit einigem Aufwand manuell nach dem Horkalkül speziell als korrekt beweisen.
Aber wenn ich sowas habe wie in einer Variablen,
steht ein Verweis auf ein anderes Objekt.
Und da können wieder Verweise auf weitere Objekte
stehen und das kann sich während der Laufzeit dynamisch verändern,
dann sind diese Ansätze eigentlich nicht mehr machbar.
So, außerdem habe ich schon gesagt, sie sind sehr aufwendig.
Deshalb gibt es als Hilfsmittel heute im professionellen Bereich maschinelle Theorienbeweise.
Das sind also Programme, sehr aufwendige, mächtige Programme,
denen gebe ich ein Programmtext und die helfen mir schrittweise, diese formalen Beweise zu führen. Sie sind dadurch ein ganzes Stück schneller als alles,
was ich mit Papier und Stift hinkriege,
aber immer noch sehr aufwendig und es ist einfach nicht so,
dass sie ein Programm nehmen können, sagen wir mal,
tausend Zahlen Java und stecken das in Theorienbeweise und dann kommt hinterher raus,
das Programm erfüllt die Spezifikation XYZ,
sondern das sind interaktive Theorienbeweise und in jedem Schritt oder jedem zweiten,
dritten Schritt werden an sie, als die Menschen,
die davor sitzen,
vor diesen Beweiser werden Fragen gestellt und dann müssen sie etwas entscheiden oder etwas eingeben,
also auch sehr, sehr aufwendig.
Deshalb Deshalb kann man eigentlich nur sagen, einen formalen,
du weißt, auf dieser Ebene, der wird heute geführt,
für kleine Rechenkerne in sehr kritischen Programmanwendungen.
Also wenn ich eine Steuerung brauche für irgendein großes Passagierflugzeug und will sicher gehen,
dass dieser Steuerung, die Steuerungssoftware im Kern das macht,
was der Pilot will oder eingegeben hat,
für diesen kleinen Ausschnitt der Größenordnung 600 Zeilen, 1000 Zeilen,
2000 Zeilen Programm würden Spezialisten so einen formalen Beweis führen.
Also für das, was Sie schon eigentlich in Ihren Übungsgruppen geschrieben haben,
mit so ein bisschen Software drumherum aus Bibliotheken,
sind so Beweise eigentlich nicht mehr führbar.
Okay, ein Schritt in Richtung mehr Anwendungstauglichkeit
hat die Theorie oder das Konzept der abstrakten Datentypen gebracht.
Die Idee dabei ist folgende. In ganz klassischen imperativen Programmen
hatte ich die Operationen, die die Programmiersprache anbietet.
Und ich hatte getrennt davon Wertetypen, beispielsweise ich habe Intager,
Long Real, irgendwas, von dieser Güte.
Und diese zwei Welten waren voneinander getrennt. Also ich habe
so die elementaren Befehle meiner Programmiersprache und ich weiß,
dass mir solche Wertemengen von einem bestimmten Typ zur Verfügung stehen.
So, aber die waren halt voneinander getrennt. Das eine
lag vor und die Definition der Wertebereiche war das andere.
Die neue Idee war, im Grunde genommen schon das vorwegnehmend,
was wir in der Objektorientierung heute ganz gut kennen.
Ich definiere Werte dadurch, Typen dadurch, dass ich sage,
ich kenne alle Operationen, die ich auf Exemplaren dieses Typs ausführen darf.
So wie sie für Objekte einer Klasse sagen,
wie diese Objekte im Inneren aussehen, da kann ich nicht reinschauen,
aber ich kenne alle Operationen, die ich auf Objekten dieses Typs ausführen darf,
denn das ist beispielsweise in der Schnittstelle der Klasse festgelegt.
Das ist Die Grundidee dabei und weil das einigermaßen gut zusammenpasst, zu sagen,
ich trenne nicht die Welten von Daten auf der einen Seite und Operationen oder Funktionen auf der anderen Seite,
sondern ich definiere das eine durch das andere,
haben die abstrakten Datentypen für uns heute noch eine Bedeutung,
deshalb wollen wir uns heute auch noch ein bisschen damit beschäftigen.
Ja. Also was will ich mit abstrakten Datentypen? Ich will etwas mehr an der Schnittstelle zur Verfügung haben,
als nur die Signaturen und ich will das deshalb haben,
damit ich mehr über die Funktionsweise meiner Programme weiß,
und zwar nur auf der Grundlage dessen,
was ich in der Schnittstellendefinition in der erweiterten Schnittstellendefinition sehe und unabhängig davon,
was im Programm im Inneren der Klasse dann beispielsweise kodiert worden ist.
Das will ich als zweigetrennte Größen haben. Ja,
wir beschäftigen uns jetzt mit diesen abstrakten Datentypen und ich will ihnen jetzt in den nächsten 20 Minuten zeigen,
wie sieht denn so ein abstrakter Datentyp,
wenn ich ihn Lind schreiben will, aus,
damit sie ihn zumindest hier einmal gesehen und vorgeführt bekommen haben,
damit sie, wenn sie in der Literatur darauf stoßen,
sagen, aha,
hatten wir doch mal in der Vorlesung.
Ich kann zumindestens einigermaßen lesen, was so eine Spezifikation soll. Also keiner erwartet von ihnen,
dass sie am Ende hier dieser Vorlesung selbst abstrakte Datentypen spezifizieren können,
aber dass sie zumindest eine Vorstellung davon haben,
was ist Sinn der Übung, weshalb macht man sowas?
Wie sieht sowas aus? So, abstrakte Datentypen in der Darstellung,
in der Definition, werden nach einem festen Schema beschrieben.
Ich habe ihnen hier ein Schema rausgezogen, da würde ich sagen,
das ist zu 90 Prozent oder 95 Prozent das, überall in der Literatur finden.
Also das ist so ein Schema, da sagt man,
okay, ist ein abstrakter Datentyp, der wird definiert durch die Angabe der Typen,
der Funktion, der Axiome und der Vorhobedingungen.
Ich zeige ihn jetzt an einem kleinen Beispiel, einem etwas größeren Beispiel,
wie sowas aussehen kann, damit sie eine Vorstellung davon bekommen.
So, wir machen ein ganz einfaches Beispiel aus der,
sagen wir, Oberstufenmathematik, die natürlichen Zahlen und ihre Definition über die Piano-Axome.
Wie kann ich natürliche Zahlen definieren, wenn ich sage,
ich gebe nur die Funktionen an,
nach denen sie arbeiten sollen, eine natürliche Zahl,
und habe das hier in so ein ganz einfaches Schema reingebracht von einem ADT.
Das Schema besteht nur aus Tüten, Funktionen und Axiom.
So, da sehen wir uns die Reihe nach an.
Welche Typen werden definiert durch den abstrakten Datentyp natürliche Zahl, genau einer.
Ich brauche nämlich nur die natürlichen Zahlen, sonst will ich,
brauche ich keine weiteren Typen und ich will auch keine weiteren hier definieren.
Also schreibe ich hin unter Type, nut,
so nenne ich halt mein abstrakten Typ
Welche Funktionen brauche ich denn? Naja, wer sich noch
an seine Schulmathematik erinnert, der wird sich vielleicht daran erinnern.
Ich brauche im Grunde genommen für die Piano-Axome 3. Null ist
eine natürliche Zahl. Dann ist hier definiert eine Funktion Suck für Successor.
In der Mathematik hat sich das dann vermutlich so angehört.
Ich habe eine Nachfolgerfunktion und der Nachfolger einer natürlichen Zahl ist immer eine natürliche Zahl.
Wir erinnern sich vielleicht noch, dunkel. Und dann Außer
der Nachfolgeroperation brauche ich noch die Addition und die Addition,
die funktioniert halt so, dass ich sage,
ich habe eine Funktion Ad,
die nimmt zwei natürliche Zahlen und die liefert wieder eine natürliche Zahl oder anders ausgedrückt.
Die Addition zweier natürlicher Zahlen ist wieder eine natürliche Zahl.
Damit haben sie aus Sicht der Mathematik komplett beschrieben,
was die natürlichen Zahlen sind und wie ich konstruktiv an sie herankomme.
Ich habe die Null, ich habe die Nachfolgeroperation oder Funktion
und ich habe die Addition.
Und das ist hier auch so hingeschrieben.
Ich will Ihnen das also hier einmal zeigen oder vorzeigen,
wie das dann aussieht
Drei Funktionen, die haben drei Namen, null,
SUC und ADD und dahinter ist die Definition, null,
die braucht nichts an Eingabegrößen, sondern rechts vom Falsch steht das Ergebnis,
sie liefert eine natürliche Zahl.
Die Funktion SUC nimmt eine natürliche Zahl und nach dem Fall das Ergebnis,
dann liefert sie wieder eine natürliche Zahl und die Funktion ADD,
die nimmt zwei natürliche Zahlen,
das ist durch dieses X oder Kreuz dazwischen gekennzeichnet,
also die nimmt zwei natürliche Zahlen und rechts vom Falsch steht das Ergebnis,
wieder ein natürliches So, das sind also die drei Funktionen,
die für natürliche Zahlen erforderlich sind und definiert sind.
Und jetzt will ich so ein bisschen,
und das ist dann der Punkt, auf den es uns ankommt,
weil Funktionen an der Schnittstelle über ihre Parameter und Namen definieren,
das konnten wir schon lange.
Bei Interfaces, aber hier wird jetzt so ein kleines Stück
Semantik dazugefügt, nämlich zwei Aktionen sind hier vorgeführt.
Und das erste Axium sagt, wenn ich null zu einer
Zahl I addiere, kommt die Zahl I
wieder heraus
Deshalb, add von irgendeiner Zahl I und null gibt I.
Und dann so kommentativ die Reihenfolge,
in der ich die Funktion Successor und Ad ausführe,
die kann ich austauschen, ohne dass ich das Ergebnis verändert.
Also wenn ich erst den Nachfolger von J berechnen lasse und
dann I zu dem Ergebnis addiere, dann kommt das Gleiche raus,
als wenn ich erst I und J addiere und dann die Nachfolge von oder so.
Das ist das, was hier stehen soll.
Das ist das Schema, was sie lesen können sollten.
So ein bisschen so stellt man das bei abstrakten Datentypen in der Mathematik dar.
Und jetzt als Vorbereitung auch für den zweiten Teil,
der dann nach der Pause kommt,
will ich Ihnen das Ganze einmal am Stack nochmal vorführen und dann probieren wir mal,
ob Sie ein bisschen davon schon lesen können.
Achso, ich habe das einmal noch hier formal aufgeschrieben,
das, was man dazu sagt,
können Sie hinterher nachlesen im Skript
Wir wollen also insgesamt im Rest hier der Vorlesung zusammen einen Stack verstehen und uns ansehen,
was Fred, Fred, Fred, Fred, Fred.
Und was Fred dazu gebaut hat. Also einen Stack kennen
wir ja schon aus der Praxis, aus den Übungen.
Und wir wollen einen Stack von Tee bauen, wobei Tee für irgendeinen beliebigen Typ steht.
Wir wollen aber während wir den Stack hier definieren,
noch nicht festlegen, wie Tee aussieht.
Also Tee steht jetzt nicht für irgendetwas Konkretes,
sondern da können wir irgendeinen konkreten Typ für einsetzen.
Also von mir aus in oder Konto oder Vertrag oder irgendetwas.
Üblicherweise hat ein Stack vom Namen her bekannte Operationen oder Funktionen,
Push, Pop und Top.
Und ich muss ein bisschen etwas über meinen Stack erfahren,
nämlich einmal, ob er leer ist.
Und zum Zweiten muss er irgendwie in die Welt kommen und
das mache ich über Empty und SMT. Wir haben auch alle,
wenn wir mal damit gearbeitet haben, eine Vorstellung,
wie so ein Stack funktioniert, Der Stack, das ist dieser Mensa-Teller-Stapel,
wo ich mir immer oben ein Tablett oder einen Teller wegnehme oder hinterher sauberer Teller obendrauf tue.
Das ist das Grundprinzip vom Stack. Immer nur ein Element
obendrauf oder von oben weg. In der Literatur finden sie oft ohne weitere Erklärung,
weil die Autoren oder Innen denken,
das sei jedem bekannte Informatik studiert habe, seien sie,
da steht dann, dieser Datentyp funktioniert nach dem Lifo-Prinzip und Lifo heißt einfach Last in first out.
Und dann steht dann Oft im Gegensatz zu einem FIFO-Prinzip,
FIFO, wenn Leaveful, Last Hand First Out ist,
FIFO-Prinzip ist first in first out, also für eine Q beispielsweise.
So, das können wir so hinschreiben, was wir von einem Stack erwarten.
Wenn wir uns jetzt so in Richtung abstrakter Datentyp bewegen,
dann sehen wir mal, was daraus wird. Wir haben hier wieder unser Schema,
dieses Mal etwas vollständiger, also wir müssen uns was übertypen,
überlegen, Funktion, Axiome und die Pre-Conditions,
also die Vorbedingungen, wir uns noch mal ansehen,
weil die für unseren Fall auch ganz spannend sein können,
wenn wir das in die Programmierung umsetzen.
Was brauchen wir für Typen? Naja, wir wollen einmal natürlich
unseren Typ Stack von Tee definieren und irgendwo anders besorgen wir uns Norbulien,
weil Bulien brauchen wir auch, um ihn vernünftig beschreiben zu können.
So, also der Teil ist, denke ich, relativ einfach.
Jetzt habe ich mal alle Funktionen, die wir definieren wollen.
Habe ich jetzt mal hingeschrien und jetzt sage ich zu jeder Funktion,
das, was die Funktionen aus unserer Sicht leisten soll.
Und dann sehen Sie das in der Notation eines ADTs und
Sie sollen mal versuchen, diese Definition zu verstehen.
Da werde ich dann mit Ihnen so ein kleines Frage-Antwort-Spiel machen.
So, die erste Funktion MT, die ist relativ einfach,
die soll den leeren Stack liefern.
Push. Push nimmt einen Stack und ein Element
Als Eingabeparameter der Funktion und liefert einen Stack zurück,
wobei wir davon ausgehen, dass dieser Stack das Element,
was wir der Funktion mitgegeben haben, enthält.
Das ist die zweite Operation oder Funktion. So,
die Funktion Pop nimmt einen Stack und liefert einen Stack als Ergebnis mit dem Effekt,
dass der gelieferte Stack das oberste Element weniger hat.
Also das oberste Element nicht mehr hat.
Mit anderen Worten, wir räumen das oberste Element von einem
Stack ab
Wenn wir uns das oberste Element ansehen wollen,
dann verwenden wir die Funktion top, der geben wir einen Stack
mit und die Funktion liefert das oberste Element dieses Stacks.
Ja, Emte ist wieder recht trivial, Emte nimmt einen Stack und liefert true,
wenn der Stack leer ist und ansonsten liefert die Funktion Falls.
Jetzt habe ich das, was hier steht,
in Stack-Notationen übertragen, aber weil man manchmal nicht so richtig weiß,
wie man das machen soll, haben sich da den Fehler eingeschlichen.
Also jetzt sehen Sie sich rechts die Beschreibung an,
was die Funktionen sollen und versuchen zu verstehen,
ob links das einfach von den Signaturen her steht,
was rechts beschrieben ist.
Ich lese nochmal vor, kann sich dabei ansehen,
Empty liefert den leeren Stack.
Push nimmt einen Stack und ein Element und liefert einen Stack,
der das Element enthält
Pop nimmt einen Stacker und liefert einen Stack, ohne das oberste Element. Top,
nimmt einen Stack und liefert das oberste Element und ist MT,
liefert einen Stack und true,
wenn es der Claire ist, ansonsten falls.
Das soll links als Schnittstellendefinition stehen,
dass einige Pfeile so einen kleinen Querstrich durchhaben,
das ist jetzt kein Optikfehler,
kann ich gleich noch einen Satz zu sagen.
Sollte sie jetzt aber nicht weiter
So. Haben Sie es sich angesehen? Haben Sie versucht zu verstehen,
ob links das formal notiert ist, was rechts dazu steht.
Durch Handzeichen wollen wir jetzt die uns mal ansehen,
wo sind die Fehler. Wer ist der Meinung,
dass die Funktionsschnittstellendefinitionen von Empty falsch ist?
Bitte Handhem? Wer ist der Meinung,
dass die Funktionsdefinition von Push falsch ist, für Darmhelm?
Okay. Wer ist der Meinung,
dass die Funktionsdefinition von Stack
falsch ist
Aha, ja. Wer ist der Meinung,
dass die Definition von Top falsch ist?
Okay. Ja, und wer ist der Meinung,
dass die Definition von ISMT falsch ist?
Okay, das sind die Fehlerhaften. Da waren ja einige
, haben das ja doch schon sehr klar gesehen.
Und hier ist die korrekte Definition
Gut. Also, jetzt können wir zumindestens aus so einer
natürlichsprachigen Definition und einer eher formal gehaltenen Definition so einigermaßen erkennen,
was ein ADT beschreiben soll.
Aber das sind ja nur die Schnittstellenbeschreibungen. Die sind ja noch
relativ einfach. Wir gucken uns jetzt die Axeume an.
Das machen wir also in einer ähnlichen Art und Weise.
Das zeige ich Ihnen dann auch hier. Wir gehen Sie
der Reihe nach durch. Für jeden Stack vom Typ T
gilt. Das erste Aktion soll sagen,
wird ein Element X mit Push auf den Stack S gelegt
Und dann die Funktion top gerufen, erhalten wir wieder das Element X. Also,
das heißt, wenn ich Push und top nacheinander aufrufe,
für ein Element X und einen Stack,
dann habe ich hinterher wieder X in der Hand.
Das zweite ist, wird ein Element X mit Push
auf den Stack gelegt und dann die Funktion Pop gerufen,
erhalte ich wieder den ursprünglichen Stack
Das Nächste wird die Funktion SMT nach der Funktion Empty gerufen,
erhalten wir True. Und dann wird die Funktion,
ist MT nach der Funktion Push mit S&X gerufen,
erhalten wir Falls. Also hier wird zu beschriebenen Axiomen,
wie ist das Zusammenspiel von Funktionen auf einem Stack mit einer Semantik,
die wir im Hinterkopf haben.
Gut, wer ist der Meinung,
dass das Aktionen für Top und Push falsch dargestellt ist.
Wer ist der Meinung, dass das Aktionen für Pop und Push
falsch dargestellt ist? Wer ist der Meinung, das ist Empty,
das Aktionen falsch dargestellten? Und wer ist der Meinung,
dass das Aktionen für Push und ist Empty,
falls es dargestellt worden ist?
Okay. Ja, das haben sie also mittlerweile,
denke ich, ganz gut erkennen können.
Die richtige Notation ist so
Okay. Zum Abschluss sehen wir uns dann noch einmal an den,
ganz kurz zu den Pre-Conditions. Da habe ich gesagt,
das ist etwas auch sehr Spannendes, weil wir das auch hinterher programmiersprachlich,
wenn wir wollen, umsetzen können in unserem Vertragsmodell.
Die zwei Pre-Conditions, die wir notieren wollen, ist,
die Funktion Pop kann nur gerufen werden,
wenn der Stack nicht leer ist.
Ich denke, es ist nachvollziehbar,
denn wenn das Stack leer ist,
was soll abgeräumt werden
Das Gleiche gilt für die Funktion top. Auch die kann
nur berufen werden, wenn das Deck nicht leer ist,
weil welches Element soll ich sonst als Top-Element zurückgeben?
Und das sind die beiden Pre-Conditions. Und wer sich so
ein bisschen schon mit Vertragsmodell und Java und Zusicherung beschäftigt hat,
der kann sich vermutlich schon vorstellen, okay,
das kann ich auch in einem Programm als Zusicherung an diese Operationen reinschreiben,
dass ich Pop, wenn sie als Operation vorlegt,
nur rufen darf,
wenn mein Stack nicht leer ist und das Gleiche top
Soweit zum ADT. Hier wieder hinterher was zum Nachlesen.
Wenn ich das den abstrakten Datentyp als Konzept direkt in die
objektorientierte Programmierung überführen will, muss ich an einigen Stellen aufpassen.
Dann merke ich, das knirscht ein bisschen. Hier wollen wir nur festhalten,
aber es ist zumindest der Ansatz, der am weitesten Programmiersprachlich umgesetzt werden kann.
Man muss ein paar Gedanken zu machen, aber es funktioniert.
Und jetzt nach einer kurzen Pause werde ich dann an Fred weitergeben,
der ihnen mal so zeigt,
Wenn ich die Grundidee von einem Stack, der funktional gebaut ist,
der also mit durch Funktionen definiert ist,
wenn ich den hebe und keine Seiteneffekte haben will,
was dann dabei rauskommt.
Fünf Minuten Pause, danke. Gut. Braucht ihr noch
Pause oder kann es losgehen?
Alle sehen aufmerksam aus
So, von mir aus kann es losgehen. Ich hoffe,
ihr habt alle diese Folie hier gelesen, die erste.
Es ist immer so, wenn ich Projekte vorbereite,
dann gibt es immer ganz, ganz viele Spezialfälle,
über die ich eigentlich gar nicht reden will und dann denke ich,
will ich jetzt drüber reden oder nicht und dann schmeiße ich sie wieder raus.
Also das, was ich euch heute zeige, ist stark vereinfacht.
Okay, wir werden heute ein bisschen,
ich glaube zum ersten Mal auch über Generics reden.
Also Heinz hatte ja schon gesagt,
man will ein Stack von Tee implementieren,
was das Tee genau ist,
will ich aber noch gar nicht definieren
Das heißt, wir wollen eigentlich jetzt so eine Art eigene Sammlung bauen.
Und sowas habt ihr, glaube ich, in SE1, das SE2 noch nie gemacht.
Das heißt, Generics ist jetzt neu, dann dieses Ganze,
wie baut man unveränderliche Sammlung, ist jetzt neu,
was kann ich dann alles damit Sinnvolles machen, ist neu.
Und ich wollte nicht zu viele technische, komplizierte Details erwähnen. Deswegen sind so ein paar Dinge,
wenn ihr zum Beispiel noch nie gehört habt,
was Co- und Contra war, jetzt ist es halt froh,
ich werde es nicht erwähnen, aber in der Realität ist das wichtig.
Gut. Java 8 ist seit März draußen,
ich weiß nicht, ob das irgendjemand gemerkt hat,
aber 8 ist so, einige sagen,
die größte Neuerung, die es jemals in Java gab,
noch größer als Java 5, wo die Generics reinkamen.
Auch da werde ich nur ein bisschen drüber erzählen.
Man könnte mehrere Vorlesungen nürwird Java 8 machen.
Das soll hier also auch nur ein kleiner Einblick sein.
Gut, das ist also das Formale, also verklagt mich bitte nicht.
Der Hammer hier oben soll also der Gerichtssaal sein.
Bitte mich nicht verklagen, dass ich irgendwas verspreche und dann nicht einhalte.
Gut. Ich hatte in den Projekten öfter mal ein und
dasselbe Problem gesehen und das immer wieder mit Studenten angesprochen.
Es geht also hier um diese Vormerkarte,
die ihr irgendwann mal bauen solltet. Und auf dieser Vormerkarte
hat man dann, da gab es verschiedene Möglichkeiten,
was man da für eine Sammlung genommen hat.
Aber irgendwie hat man Kunden in
so einer Sammlung sich gemerkt
Okay, so haben das die meisten von euch gebaut.
Und da gab es da diverse Operationen. Man wollte irgendwie sicherstellen,
dass man nicht zu viel auf die Vormerkarte rauftut und so weiter.
Und eigentlich sieht das ja alles ganz gut aus.
Vielleicht erinnert euch ja, vielleicht erinnert sich noch jemand von euch,
wo hier das grundlegende Problem war,
was gar nicht so offensichtlich ist in diesem Quelltext.
Kann sich noch irgendjemand erinnern oder sieht es spontan,
dass da irgendwas nicht so ganz koscher ist, ja?
Ich weiß nicht, wie das bei dir jetzt alle vor mir kam,
dass du einfach, wie viele, keine Lust hast.
Richtig. Warum ist das ein Problem, dass ich
hier in Geld alle vormerke, einfach die Liste rausreiche?
Weil die Dachnuss, dass man dann da auch
Sachen anbringen kann
Das war lustig, kurz. Ja, was sagt dein Nachbar?
Ich würde sagen, dass man sie von außen verändern kann,
obwohl eigentlich Feuer, das ist so die Ansage.
Richtig, genau. Also man kann sie von außen verändern.
Der Klient könnte also sich die Liste holen und könnte dann
zehn Vormerke einfügen und da könnt ihr auch Nallreferenzen einfügen oder irgendwelche Vormerke,
die da eigentlich gar nicht rein dürfen oder irgendwas doppelt oder so.
Okay, das ist also immer ein Problem,
dass man im Hinterkopf haben muss, wenn ich Objekte rausreiche,
kann dann der Klient damit irgendwelchen Unfug machen.
Okay, wie könnte ich das denn jetzt beheben?
Was ist denn die Lösung des Problems
Ja, irgendjemand anderes vielleicht nur, ja.
Eine neue Geschichte. Eine neue Liste erstellen und die rausgeben
, genau das ist die Möglichkeit eins.
Das ist diese Möglichkeit hier oben, die Frau am Kopierer.
Man kopiert sich einfach die Liste, ja. Wenn ich eine Kopie rausgebe,
ist mir egal, was der Klient mit der Kopie macht,
ich bin davon dann nicht mehr betroffen.
Die zweite Möglichkeit ist, vielleicht kennt ihr das gar nicht.
Es gibt hier so eine ganz nette Methode,
da kann ich eine Liste reinstecken und bekomme dann eine Liste zurück,
aus der ich nur noch lesen kann.
Wenn ich versuche zu schreiben, dann knallt es zur Laufzeit.
Also vielleicht hätte ich hier statt einem Aquarium mehr so
einen elektrischen Zaun hinmachen sollen.
Also wenn ihr da versucht heranzupacken,
dann bietet sofort
eine Acception
Das sind also die beiden Möglichkeiten, die man klassisch wählt
in Java. Es gibt noch eine dritte Möglichkeit,
die nicht so weit verbreitet ist im Java.
Ich mache die Sammlung einfach von vornherein unverändert.
Ich sage, die Liste kann man gar nicht verändern.
Und der erste Instinkt ist so, dass man denkt,
eine Liste, die man nicht verändern kann, ist doch sinnlos.
Was soll sowas? Aber es gibt tatsächlich viele Sprachen,
in denen solche Unfälle unveränderlichen Listen eingebaut sind. Und ich dachte,
wir bauen das heute einfach mal, damit ihr seht,
wie man es baut und was das dann für Konsequenzen hat.
Gut. Okay, wenn man mal so guckt in der
Literatur oder in anderen Sprachen, wie sie es umsetzen,
zum Beispiel Skala, Und man schaut sich mal an,
so ein Klassendiagramm von so einer unfänglichen Sammlung in diesem Fall
ein Stack, dann sieht das erst mal ganz komisch aus.
Wir haben in SE1, hatten wir immer so
ein Objekt für die Sammlung an sich und dann hatte man
irgendwo anders intern seine Knoten und hat die Knoten noch gar nicht rausgegeben.
Das sieht jetzt hier völlig anders aus. Wenn man so hier erst mal, ja,
die Schnittstelle eines solchen Stacks und dann haben wir zwei verschiedene Klassen,
die das realisieren, beziehungsweise das ist hier sogar eine abstrakte Klasse und das sind beides noch einfach Untertypen.
Okay, ich habe also einmal einen Untertyp, der,
ja, dafür steht, ich habe ein Element und ich habe einen anderen Untertyp,
der dafür steht, ich habe gar kein Element mehr,
das Stack ist jetzt leer.
Ja, das heißt also, ich baue Stacks zusammen,
indem ich sage,
ich gehe erst mal von dem leeren Stack aus und dann
kann ich nach und nach
weitere Elemente hinzufügen
Okay, was hat er denn so vor Operationen?
Kommen die uns irgendwie bekannt vor? Also dieses ESEMT,
die haben wir ja eben schon gesehen. Ich möchte also
einen Stack fragen können, ob er leer ist.
Da wurde jetzt zum Beispiel ein Exemplar dieser Klasse, würde immer sagen,
oh, das ist genau falsch und hier müsst ihr eigentlich zustehen.
Der würde immer sagen, dass er leer ist und
der würde immer sagen, dass er nicht leer ist.
Gut, ich habe jetzt noch eingebaut, das war in
Heinz vor ihr nicht. Das ist einfach nett,
weil man dann noch sehr viele zusätzliche Nachbedingungen formulieren kann.
Ich kann auch die Größe von so einem Stack abfangen,
also wie viele Elemente sind drauf?
Und dann zwei Operationen, die wir eben auch gesehen haben.
Ja, also was ist oben auf dem Stack drauf?
Das darf ich hier gar nicht fragen,
deswegen habe ich das mal rot gekennzeichnet,
da fliegt dann
eine Acception
Oder ich kann auch sagen, wenn ich jetzt das oberste Element entfernen würde,
was würde denn dann für ein Reststeck übrig bleiben?
Und das darf ich bei dem Leons Deck natürlich auch nicht machen.
Da ist ja kein Restdeck geworden. Also diese beiden Operationen
darf ich gar nicht aufrufen auf dem leeren Stack.
Während hier, ja, ist das kein Problem.
Wenn ich also mindestens ein Element habe,
dann kann ich mir das oberste Element angucken und ich kann mir auch anschauen,
was für ein Stack liegt denn noch darunter,
was für ein Reststeck sozusagen.
Ja, und wie wird das Ganze dann implementiert?
Das habt ihr vielleicht auch schon gesehen,
als mir doppelt verkettete Listen implementiert habt.
Also ich habe einfach eine Referenz auf das Element,
was oben auf dem Stack drauf ist.
Und dann habe ich eine Referenz auf den restlichen Stack
Okay, das ist jetzt nicht so anschaulich, was ich gerade gesagt habe.
Man sieht ja in so einem Klassendiagramm immer nur die statische Struktur.
Deswegen habe ich nochmal ein Objektdiagramm gezeichnet. So würde also
ein Stack aussehen, auf den ich vier Videos draufgetan habe.
Ich gucke mir immer ganz gerne so Videos an über Programmiersprachen, Konzepte und so.
Ich habe also vier Videos auf den Stack getan.
Wie habe ich das gemacht? Ich bin erstmal von dem
leeren Stack ausgegangen. Da war also kein Video drin.
Und dann habe ich anschließend gesagt, ich möchte jetzt
dieses Video hinzufügen, dieses Video über Language Integrated Query.
Das möchte ich hinzufügen. Dann kam das auf den Stack
drauf. Dann habe ich anschließend gesagt,
ich will auch noch dieses Video haben.
Dann kam das auf den Stack drauf. Also man baut
das sozusagen so von rechts
nach links auf
Aber es ist jedes Mal sozusagen ein neuer Stack.
Also der leere Stack ist nach wie vor leer.
Dieser Stack mit einem Element hat nach wie vor nur ein Element.
Dieser Stack mit zwei Elementen nach wie vor nur zwei Elemente und so weiter.
Ich ändere also nichts. Ich werde hier also niemals irgendwelche
Referenzen umbiegen oder so. Das ist nicht erlaubt bei unveränderlichen Songs.
Ich kann also immer nur vorne einfügen. Das ist also
eben ein Stack. Also wenn man eine Anwendung hat,
wo man dieses Verhalten braucht,
das ich immer vorne einfüge und vorne auch wieder entferne,
dann ist das gut.
Ansonsten ist es jetzt nicht so wahnsinnig empfehlenswert.
Also im konkreten Kontext der Formel-Karte würde ich sagen,
Vormerker werden ja an einem Ende hinzugefügt und unterm anderen wieder entfernt.
Da bietet sich ein Stack eigentlich überhaupt nicht an.
Aber so als Konzept, wie sowas generell funktioniert,
kann ich es
da trotzdem zeigen
Gut. Dann würde ich mal sagen, ach so,
für spätere Folien habe ich das Ganze nochmal vereinfacht.
Also wenn man nicht immer diese komplizierte Geflecht sehen will,
habe ich das Ganze mal so ein bisschen komprimiert.
Das soll also derselbe Stack sein. Gut,
jetzt gehen wir mal in Eclipse und schauen wir mal,
was ich da vorbereitet habe. Ich habe also schon mal vorbereitet,
dass ich mir so ein paar interessante Videos,
die ich irgendwann mal in ferner Zukunft gucken will,
wie ich so in mein Programm aufgenommen habe.
Aber wir fangen mal an in den Programmen mit der Klasse Stack.
Fangen wir mal ganz oben an. Ich habe die also schon so ein bisschen ausgefüllt,
aber ich habe die, das Vertragsmodell extra leer gelassen,
damit wir das so zusammen und so ein bisschen eignen können, okay?
Das habt ihr ja gerade eben gesehen,
theoretisch und jetzt kommt es praktisch einsetzen.
Gut. Also bei der Operation ist Emty,
kann ich vertraglich nichts irgendwie zusichern. Also die Tatsache,
dass dann Bulien zurückkommt, sagt ja schon das Typsystem.
Ja, das sehe ich im Rückgabetyp, viel mehr kann ich da nicht machen.
Also da kann ich jetzt nicht noch was Zusätzliches zusichern.
Wie ist das mit der Größe eines Stacks?
Ich gebe also in einen Into zurück, der ist ja
irgendeine Zahl zwischen minus zwei Milliarden und plus zwei Milliarden.
Kann ich das jetzt irgendwie noch sinnvoll einschränken,
dass ich sage, lieber Klient, du kannst dich darauf verlassen,
dass hier nur solche Werte zurückkommen?
Bei einem Stack, ja. Ja, wahrscheinlich kann man
ja mal positiv bewerten. Ja, und zumindest nicht negativ.
Also ich könnte sagen, die Zahl, die da zurückkommt,
ist immer mindestens neu
Jetzt noch nicht so wahnsinnig spannend, okay. Gut,
dann geht's weiter. Bei dem Top habe ich jetzt eine Vorbedingung.
Warum brauche ich bei dem Top eine Vorbedingung und welche Vorbedingung
wäre da sinnvoll? Da meldet sich jemand, ja?
Ich habe diese ganze Nall-Diskussion jetzt erstmal ausgeklammert,
weil das so ein bisschen immer sehr technisch wird.
Also ich wollte jetzt bei den theoretischen Konzepten lernen.
Genau, also er darf nicht leer sein. Ich kann
also sagen, not is mT. Camp? Jetzt würde ich
auch ein Assert dazu schreiben, das mache ich jetzt nicht.
Es geht hier erstmal hauptsächlich um die Konzepte. Gut,
das Top darf ich also nur aufrufen,
wenn der Stack nicht leer ist.
Gut,
das haben wir noch
Pop, das ist genauso. Das darf ich auch nur aufrufen,
wenn das Deck nicht leer ist. Kann ich jetzt
irgendwas zusätzliches aussagen, was etwas über das Ergebnis aussagt?
Ich habe ja schon so einen kleinen Tipp gegeben,
dieses Size, kann man bei sehr vielen Nachbedingungen sinnvoll einsetzen.
Sieht jemand, dass man etwas über die Größe des Stacks
aussagen kann, der bei diesem Pop zurückkommt?
Also wenn ich über ihn zehn Elemente habe und ich gucke mir an,
was ist denn unter dem obersten Element,
wie viel sind da noch,
ist das eine zufällige Zahl oder hängt die irgendwie zusammen mit
der Zehn
Ja. Die Größe ist um einen Kleiner, genau.
Ich könnte also sagen, die Größe von dem Stack,
die da zurückkommt, ist die eigene Größe, minus eins.
Ja, dann habe ich das formal festgehalten. Das ist doch
auch ganz nett. Gut, was haben wir noch?
Oh, hier kommt einiges. Push. Da kann ich also
einiges zusichern. Womit, was fällt mir dann als erstes
ein? Wenn ich etwas auf einen Stack drauf tue,
egal welches Deck das vorher war, wird er ja sicherlich nicht leer sein.
Ich kann dem Klienten also zusichern,
wenn du etwas oben auf einen Stack drauf tust,
dann ist das Ergebnis kein leerer Stack.
Das wird ja mindestens das Element drauf sein,
was du gerade aufgetan hast
Okay, wollen wir vielleicht nochmal über die Größe nachdenken?
Wie groß ist denn der Stack,
wenn ich ein Element oben rauf tue, ja?
Das ist Nummer eins größer, ne? Also kann ich sagen,
result.size is, glaube ich, this.sice, plus eins.
So, jetzt habe ich da noch zwei Entschuhes.
Was könnt ihr denn noch hin? Ah, richtig,
das war auch auf den Folien von Heinzdorf, genau.
Also ich habe ja meinen momentanen Stack und ich habe dieses
Element und ich kombiniere die jetzt zu einem neuen Stack und
ich könnte jetzt an diesen neuen Stack irgendwie wieder fragen,
was ist denn oben bei dir drauf und was ist unter dem obersten Element?
Ich könnte also sagen, wenn du auf dem Ergebnis das Top aufrufst,
dann wird er genau das Element rauskommen,
was du mir gerade oben raufgetan hast sozusagen.
Gut, und wenn du dir das Ergebnis fragst,
was liegt denn unter dem obersten Element?
Was wird dann zurückkommen? Ja, genau. Sehr schön
, ne? Also ich glaube, dann haben wir
alle Nachbedingungen hier schön formuliert. Sehr gut. Was haben
wir noch? Das müssen wir noch implementieren. Ja,
jetzt kommt schon so eine komische Merkwürdigkeit von Java.
Ich habe das am Anfang so ein bisschen übersprungen.
Also ich kann bei einer Klasse oben so einen Typ Parameter angeben.
Das ist also auch dasselbe TV auf den Folien und
dass man hier diese eckigen Klammern nimmt.
Aber dieser Typ Parameter bezieht sich nur auf die Methoden für
die Exemplare dieser Klasse und für statische Methoden muss ich den nochmal definieren.
Das ist jetzt schon so eine Ecke,
wo ich eigentlich gar nicht drüber reden will.
Okay, akzeptiert einfach bei statischen Methoden,
muss man diesen Typ Parameter einfach nochmal hinschreiben.
Gut, was kann ich denn über den leeren Stack aussagen?
Kann ich über den irgendwas Sinnvolles aussagen, was auf jeden Fall gilt?
Was gilt auf jeden Fall für den leeren Stack?
Ja. Er ist leer, richtig. Der leere Stack ist leer.
Sehr gut. Das heißt also, das Ergebnis ist auf
jeden Fall leer. Gut, man könnte jetzt auch noch sagen,
dass die Größe null ist, noch warum nicht,
fällt mir gerade ein, kann man ja eigentlich auch noch euch machen.
Gut, was haben wir noch? Ah, okay, das führt jetzt schon ein bisschen weiter. Jetzt sollten wir uns vielleicht überlegen,
wie wir diese beiden Klassen,
ich zeig dir noch mal ganz kurz, so,
wir haben bis jetzt ja Vor- und Nachbedingungen formuliert für diese ganzen Operationen,
ja, damit sind wir jetzt durch,
Push und Empty müssen wir noch implementieren in der Klasse Stack und die anderen vier Operationen müssen wir noch in den konkreten Unterklassen implementieren,
die Klassen gibt es noch gar nicht,
die müssen wir uns jetzt bauen.
Also ich würde sagen, Nil ist einfacher,
damit fangen wir mal an
Also sage ich jetzt Eclipse, dass ich eine neue Klasse haben möchte.
Die nenne ich mal nüll und den Rest schreibe ich von Hand.
Das kann man zwar theoretisch auch da einstellen,
dass die erbt, aber ich finde das immer einfacher,
das einfach selbst zu schreiben.
Ach so, ich muss natürlich jetzt auch hier wieder den Typ-Harameter angeben.
So, wo ist denn die geschweifte Klammer zu?
Ah, da ist sie. Gut, Public muss die eigentlich nicht sein,
das ist ein Implementationsdetail, dass es diese Klasse gibt.
So, warum beschwert sich Eclipse jetzt?
Ich habe eine Klasse mit vier abstrakten Methoden und behauptet,
dass das ein Subtyp ist und diese Klasse ist nicht
abstrakt,
was muss ich jetzt machen
Ja. Wer war das? Betoni, wie war die
Mittlere dritte? Genau, es gibt die Methoden,
die noch nicht unter dem Litz sind und die muss ich implementieren.
Also sage ich ja lieber das Eclipse, mach das doch mal
für mich. Jetzt sind die alle da und ja,
jetzt schauen wir mal, dass wir das sinnvoll implementiert kriegen.
Oh, ich sehe gerade die Formatierung ist nicht so schön,
wie sie haben will, das ist kein Problem.
Das kann ich ganz schnell beheben. Das importieren wir einmal kurz.
Ich sage, ich hoffe, das ist jetzt besser.
Ja. Gut. Was soll denn bei dem IS-Empty
beim leeren Stack zurückkommen
Im Gegensatz zu den Folien dabei war ja leider der Fehler drauf.
Ist der Lehres der Kleer? Ja, der ist leer.
Da soll so ein Schuh zurückkommen. So. Wie ist die Größe
eines leeren Stacks? Da muss ich gar nichts mehr machen.
Das ist null, ne? Das ist also schon korrekt
von Eclipse geraten, dass da null zurückkommen soll.
So, wie ist es jetzt mit den Operationen Top und Pop?
Da hatte ich ja gesagt, die darf man eigentlich gar nicht aufrufen.
Ja, da können wir eigentlich sofort eine Acception werfen.
Sagen wir mal hier, Frau, yo, sursion,
wie heißt sie? Session Error. Das ist immer die,
die auch von einem Asserd geschmissen wird,
wenn die Bedingung Fouls ist.
Sagen wir mal,
Vorbedingungen verletzt
Ja, wie war die Vorbedingung? Nicht ist MT,
ne? So. Dann kam das weg.
Und das muss natürlich genauso auch bei dem Pop rein.
So. Damit ist der leere Steck im Typ.
Das war ja einfach, ne? Der leere ist,
der kann ja nicht besonders viel.
Gut, dann implementieren wir mal den nicht leeren Stack.
Das muss nicht Public sein.
Das kriegt ein Typ Parameter und Extended Stack for Tea
So. Cons, wofür steht das? Das ist eine sehr
gute Frage. Also früher hatte man ja nicht so viel Speicherplatz
und hat alle, wir zeichnen so kurz wie möglich gemacht.
Wofür steht Cons? Also Cons steht sicherlich,
wird zusammen und das S steht möglicherweise über was anderes.
Ich weiß es ehrlich gesagt nicht. Das heißt nur so viel wie,
nimm ein Bestehen, nimm eine bestehende Liste oder ein Stack und füge etwas hinzu,
warum es jetzt Cons heißt und nicht irgendwie prepant oder so,
weiß ich ehrlich gesagt nicht.
Wer hat die Frage gestellt, der kriegt einen Snickers?
Du merkst dir, dass du kriegst einen Snickers von mir.
Gut. Schauen wir nochmal ganz kurz auf die Folie.
Wir müssen jetzt ja auch Zwei Exemplarbare-Rappen hier haben,
Head on Tale, habe ich sie mal genannt.
Also das Element, was ganz oben draufliegt und dann der Rest-Stack,
das kann ich ja einfach mal hier so übernehmen.
Also, ich nenne das mal Head und... Tale.
So, jetzt beschwert er sich, hey, pass auf,
du hast hier irgendwelche Exemplarkonstanten, die du gar nicht initialisierst.
Kann ich jetzt nicht sogar einen Konstruktor automatisch generieren lassen,
der das dann korrekt macht.
Generate Constructor, Using Fields. Ich sage einfach mal,
okay, Ah, perfekt, muss ich nicht von Hand schreiben.
Gut. Ich hasse es immer, solche Konstruktoren von Hand zu schreiben.
Gut, wie ist es mit dem ISMT? Ist ein Stack,
auf dem ein Element auf jeden Fall obendrauf liegt leer?
Nee, natürlich nicht. Das ist das False,
also schon richtig. Dann, ja, wie groß ist
denn jetzt ein Stack, der nicht leer ist?
Das ist schon gar nicht so einfach, oder?
Ja. Ja, jetzt depotive fand ich auch gute Designs,
konnte in unserer heutigen Töne.
Ja, sehr schön. Also das war dann unser Tale.
Den frage ich einfach, hey, wie groß bist du denn?
Und dann hast du gesagt, einen drauf.
Das ist aber ein heutiger Begründung
Oh, danke, ich bin in der Falschmethode.
Steuerung X. Toll, dass ich euch dabei habe.
Dieses Pair-Programming mit mehr als zwei Leuten sozusagen.
Gut. Okay, top. Was muss ich denn
da zurückliefern? Also Top sagt ja, das Element,
was ganz oben drauf liegt, das ist ja einfach das Element,
wie habe ich so genannt?
Hate, das kann ich dann einfach zurückgeben,
gebe ich mal hier den Hate zurück.
So, und bei dem Pop muss ich sicherlich Hail zurückgeben.
So, gut. Sehr schön. Jetzt können wir auch
endlich dafür sorgen, dass man solche Stacks mal erzeugen kann
Das sind ja diese beiden Operationen in Stack. Also einmal,
da müsste irgendwie der Startschuss sozusagen, ist dieses MT.
Hier erzeuge ich mir also einfach mal einen neuen leeren Stack.
Jetzt kann ich ja sagen, in New Nill von Tee.
So. Und ich möchte aber auch etwas auf einen bestehenden Stack rauftun können.
Dann erzeuge ich mir einfach so ein neues Cons-Objekt.
Was muss ich da jetzt übergeben? Als erstes den Kopf,
das ist das Element, was ich übergeben bekommen habe.
Und dann ja jetzt den alten Stack.
Was ist der alte Stack? Auf welchen Stack tue ich
es drauf
Das ist wieder das This. Ich tue es ja auf mich selber drauf.
So. Gut, das kann man jetzt mal vielleicht einmal ganz kurz testen.
Ich habe hier schon so ein bisschen Testdinge vorgegeben,
aber die klammere ich mal ganz kurz aus oder kommentiere sie aus.
Also es müsste doch jetzt zum Beispiel möglich sein zu sagen,
ich möchte ein Stack, und jetzt die Sonntags ist auch komisch,
wenn man das statisch aufwürgt,
sagen wir zum Beispiel von Zahlen, der soll leer sein.
So, im Moment falschrum, ich muss erst
die Variable deklarieren.
Kann ich
das nicht
automatisch machen
Sehr gut. Also das nenne ich einfach mal A. Okay,
A ist jetzt mein leerer Stack.
Und jetzt kann ich zum Beispiel sagen, ich will auf
das A was rauftun, zum Beispiel 42. Okay.
Das muss ich natürlich auch deklarieren. Das ist ja hier
keine dynamische Sprache. Und schon auch einmal, weil es
so viel Spaß macht. C ist dann vielleicht B.frosch 97. So. Okay.
Jetzt kann ich mit diesen Stacks aber noch nicht so wahnsinnig viel machen.
Ich will sie ja zum Beispiel vielleicht mal auf der Konsole sehen.
Also wenn ich irgendwie sage, System Outprint Line A,
Und vielleicht auch mal B und C.
Ich will einfach mal sehen, dass sich auch tatsächlich nichts geändert hat.
Dann wird man jetzt nur irgendeinen Unsinn sehen. Ja,
warum sieht man jetzt unseren Unsinn? Weil natürlich das Tour String
noch nicht sinnvoll überschrieben ist. Es wäre also sinnvoll,
wenn das Tour String die einzelnen Elemente des Sechsflets
sinnvoll in geschweifte Klammern von Mekomma getrennt oder so.
Das wäre vielleicht ganz nett. Bevor wir das machen,
muss ich aber nochmal auf die Folien zurück.
Denn wir wollen jetzt ja wahrscheinlich gleich so ein bisschen Ja,
die habe ich schon. Wir wollen jetzt da wahrscheinlich irgendwie
auch iterieren können über so ein Stack.
Also ich will irgendwie mir alle Elemente mit allen Elementen irgendwas machen können.
Das brauche ich zum Beispiel für das To-String. Also das
gehört jetzt sozusagen zusammen. Das wollen wir als nächstes implementieren.
Und dazu habe ich so ein paar Folien vorbereitet,
weil dieses For each gibt es mittlerweile in verschiedenen Variationen.
Also schauen wir uns erstmal die klassische Variante an.
Das ist die klassische Variante, die ihr schon immer kennt.
Als ich damals anfing zu studieren, gab es die noch gar nicht.
Also in Java 5 konnte man Folgendes sagen oder seit Java
5 kann man Folgendes sagen.
Ich habe hier
eine Sammlung
Das ist der Elementtyp der Sammlung. Und das ist ein beliebiger
von mir gewählter Variablenname. Und unter diesem Namen kann ich dann,
kann ich dann innerhalb des Rumpfs der Schleife auf das Aktuellelement, Element zugreifen.
So wird also über alle Elemente der Sammlung einmal iteriert.
Wir nennen das die erweiterte Vorschleife in SE1,
sonst heißt es auch gerne For-Each-Schleife oder ja.
Und was da passiert, ist relativ kompliziert.
Der Compiler nimmt das also und wandelt das in etwas um,
was man vor Java von Hand schreiben musste.
For Java 5 musste man also von Hand schreiben.
Liebe Sammlung, gib mir doch mal einen Iterator.
Das sieht also im Objekt deren folgendermaßen aus der Klient,
sagt der Collection, gib mir doch mal einen Iterator
Dann erzeugt diese Collection den Iterator und liefert ihn mir zurück.
Okay, jetzt habe ich einen Iterator, was auch immer das sein soll.
Und jetzt kann ich den Iterator fragen, ob es noch
ein nächstes Element gibt, das sieht man da so hier.
Ich frage also jetzt, oder der Klient fragt den Iterator,
gibt es noch ein nächstes Element?
Ich bin jetzt mal optimistisch und er liefert Schuh zurück.
Dann kann ich fragen, super, es gibt ein nächstes Element,
was ist denn jetzt das nächste Element?
Und dann kriege ich das nächste Element zurück.
Und dann gehe ich hier wieder meine Schleife. Das ist
hier nicht Standard-OML-Notation, das habe ich mir selber ausgedacht.
Also jetzt gehe ich wieder hier hoch und frage wieder und
dann frage ich, frage ich gleich.
Also der Klient fragt die ganze Zeit, gib mir nochmal
das nächste Element, solange noch ein Star ist.
Okay. Und man nennt das auch ganz gerne externe Iteration,
weil sich nicht die Collection darum kümmert,
dass durch die Sammlung durchgegangen wird, sondern der Klient muss das sozusagen machen.
Der Klient holt sich aktiv die Elemente aus der Sammlung
raus.
Das ist ganz nett, finde ich,
dass man das nicht mehr so kompliziert schreiben muss.
Okay, das ist aber nicht die einzige Möglichkeit.
Man kann es auch sozusagen andersrum machen,
anstatt sich die Elemente selber rauszuholen, kann man der Sammlung sagen,
schieb deine Elemente doch mal in einen Konsumenten rein.
Das sieht dann, ja,
vielleicht schauen wir uns erstmal das Diagramm an,
also der Klient erzeugt sich so einen Konsumenten und sagt dann der Sammlung,
for each, das ist also einfach nur eine Methode und ich übergebe ihn in diesen Konsumenten.
Ich sage also, der Sammlung hier
hast du einen Konsumenten und dann kannst du dafür sorgen,
dass der Konsument sinnvoll die ganzen Elemente aus dir erhält sozusagen.
Also der Client holt sich jetzt nicht mehr die Elemente,
sondern er bekommt sie reingesteckt als Parameter.
Also der Kontrollfluss ist da
genau umgekehrt
Gut. Ja, es gibt jetzt also hier
so ein Consumer-Interface, auch seit Java 8.
Und in dem Consumer-Interface ist so eine Accept-Methode drin.
Und ja, wie implementiert was? Wir haben ja schon
anonyme innere Klassen kennengelernt. Also ich kann jetzt also sagen,
dieser Sammlung, ich möchte diese For-Each-Methode aufrufen.
Und das hier ist der Konsument. Das kennt ihr schon
von den Actionlisten dann, dass man das so kompakt,
in Anführungsstrichen, hinschreiben kann.
Aber das will so natürlich keiner schreiben. Also sowas konnte
man schon schreiben, sozusagen seit zehn Jahren in Java.
Aber das will man irgendwie nicht schreiben, weil das einfach hässlich aussieht.
Wenn man das vergleicht mit der klassischen Forscher,
wie sieht es einfach blöd aus,
dieses ganze New und Public Vote Accept,
das will man eigentlich alles
gar nicht schreiben
Gut. Das heißt, man hat sich
sehr lange Zeit Gedanken gemacht in der Java-Community.
Wie kriegen wir solche Programme hübscher? Also es ging los,
2006, 2007, da gab es dann konkurrierende Vorschläge.
Wie kann man das irgendwie kompakter formulieren? Und dann hat man sich 2009 entschieden,
alles, was wir bis jetzt überlegt haben,
ist Quatsch, wir fangen mal von vorne an.
Und dann hat es dreieinhalb Jahre gedauert und jetzt hat man endlich die Lösung gefunden.
Okay, die Lösung stelle ich euch jetzt einmal vor.
Die Lösung sieht jetzt so aus. Also hier oben sieht
man nochmal das, was auf der letzten Folie war.
Und hier seht ihr jetzt, wie man das Ganze in
Java 8 formulieren kann.
Und ich habe die wichtigen Komponenten sozusagen einfach mal eingefärbt.
Was einen ja eigentlich interessiert,
ist folgendes
Was muss in diese Funktion reingesteckt werden? Ein Video,
das ist also die Parameterliste, die ist hier blau.
Und was ist dann der Rumpf dieser Funktion,
dass es sich einfach nur in Grün dargestellt?
Das ist der Code, der dann irgendwann mal ausgeführt wird.
Wenn ich dieses Accept aufrufe. Gut,
und mehr brauche ich eigentlich nicht, dass alles,
was jetzt hier schwarz ist, Objekterzeugung und hier,
wie heißt die Methode und wie sie ist Public und sie liefert nicht zurück,
das brauche ich eigentlich alles gar nicht.
Das überliest man, wenn man das die ersten fünf Male geschrieben hat.
Und deswegen kann man das ab jetzt deutlich kompakter formulieren.
Ich kann also sagen, direkt,
ich fange sofort mit der Parameterliste an,
ich muss der Funktion noch nicht mal irgendeinen Namen geben.
Dann kommt so ein netter Pfeil und dann kommt der Rumpf
der Methode. Und jetzt ist es eigentlich kaum noch länger
als die klassische VE-Schleife
Es macht technisch nicht exakt dasselbe,
aber ich finde als Anfänger kann man sich sozusagen vorstellen,
dass das nur eine Kurzschreibweise für das ist.
Wenn ihr das so als mentales Modell behaltet,
dann reicht das erstmal in 95 Prozent der Fälle.
Gut, das ist aber noch nicht kurz genug.
Ich habe jetzt noch eine Folie, wo man sieht,
wie man es noch kürzer machen kann. Also,
das ist jetzt die oberste Variante auf eine Zeile zusammengequetscht,
aber immer noch mit allen Zeichen.
Und jetzt gibt es eben so ein paar Bedingungen,
unter denen man das Ganze noch kürzer formulieren kann.
Also schauen wir mal oben, was ist denn das hier eigentlich so taktisch?
So taktisch haben wir ja einfach nur einen Blog,
hier geschweifte Klammer auf, hier geschweifte Klammer zu.
Und in dem Blog steht eine Anweisung drin
Und diese Anweisung ist eine Anweisung,
die sich zusammensetzt aus einem Ausdruck und einem Simikolon.
Man nennt das Expression-Statement in der Java-Syntax. Okay,
und wenn das der Fall ist, ihr habt also eigentlich
nur einen einzigen Ausdruck mit einem Simikolon in dem Blog,
dann könnt ihr den blocken aus dem Ikone und weglassen.
Das seht ihr also jetzt hier in der nächsten Zahlung.
Das sieht schon ein bisschen netter aus. Gut, und jetzt kommt etwas,
was sehr kontrovers diskutiert wird und auch gerne missverstanden wird.
Man muss in vielen Fällen den Typ des formalen Parameters nicht mehr hinschreiben. Ja,
und sobald man ein Typ nicht mehr hinschreiben muss,
denken sofort, Java vorher mir, oh Gott,
das ist jetzt dynamisch typisiert und Hunde und Katzen schlafen im selben Bett und so.
Keine Sorge, das ist Java ist immer noch eine
statisch typisierte Programmiersprache
Der Compiler ist einfach schlau genug. Er schaut sich einfach an,
gut, was erwartet denn dieses For each,
was für ein Sammlungstyp ist dieses Videos.
Mir ist irgendwie klar, dass es um den Typ Video
geht. Du musst es nicht mehr selber hinschreiben.
Okay? Das ist also einfach nur so eine taktische Abkürzung.
Es passiert immer noch exakt dasselbe. Es wird exakt derselbe Bytecode
daraus erzeugt. Und ihr könnt auch nur Dinge mit dem Video
hinten machen und die man eben mit einem Video machen kann.
Ihr könnt jetzt nicht irgendwelche anderen Operationen drauf aufrufen.
Das ist immer noch statisch getypt. Gut,
und dann noch eine Abkürzung,
wenn man jetzt nur ein Argument hat ohne Typ,
dann kann man diese Klammern auch noch weglassen.
Das ist also jetzt die kürzeste Schreibweise für solche Lambdas.
Also ein Argument, Typ wird automatisch und Compiler inferiert.
Und dieser Ausdruck soll
dann ausgeführt werden
Gut, das ist aber immer noch nicht kurz genug.
In vielen Fällen ist es so, dass ich dann einfach
nur den Paramit einfach nur weiter delegiere an eine andere Methode.
Und dann kann man das noch kompakter formulieren. Das sieht
dann so aus. Ja, und das liest sich schon
fast wie Englisch. Videos for Each System of Printline.
Also ich will einfach alle Videos auf der Konsole ausgeben.
Ja, liest sich doch irgendwie ganz nett.
Gut, intern passieren ja super viele komplizierte Dinge.
Wenn euch das interessiert, was da genau passiert.
Also es musste sogar die Virtual Machine neu spezifiziert werden,
weil das nicht mehr abwärtskompatibel ist. Schaut euch einfach dieses
Video an. Brian Gets ist also einer der Leute,
die sich dreieinhalb Jahre lang den Kopf zerbrochen haben.
Und ich finde, sie haben eine sehr elegante rückwärtskompatible Lösung
für Java
gebaut.
Ja, und man nennt diese Funktion Lamnder-Funktionen.
Ich weiß gar nicht, ob ich das schon gesagt habe.
Lasst euch nicht verwirren von Namen, die komisch klingen.
Lamder-Funktion heißt einfach nur eine Funktion, der ich keinen Namen gegeben habe.
Das ist alles. Mehr steckt da nicht dahinter.
Gut. Okay, for each. Also dieses for each
würde ich doch jetzt gerne mal für meinen eigenen Datentyp implementieren.
Schauen wir noch mal ganz kurz zurück, wie die Signatur davon war.
Hier haben wir es. Also dieses for each würde ich
jetzt auch gerne für meinen Stack implementieren,
dass ich irgendwo mit jedem Element irgendwas machen kann.
Versuchen wir das doch mal zu implementieren.
So, das habe ich natürlich schon irgendwo vorbereitet.
Hier habe ich es schon. Ich habe das also
mal abstrakt
In die Oberklasse reingetan. Und jetzt muss ich es wieder mir überlegen,
was muss ich bei einem leeren Stack machen,
wenn ich etwas mit allen Elementen machen will?
Nicht besonders viel. Und was muss ich bei einem nicht leeren Stack machen?
Also machen wir vielleicht das mit einem einfachen Fall,
mit dem leeren Stack, da biete ich jetzt also Eclipse
das wieder automatisch, um mich zu implementieren.
Dann haben wir das hier unten. Ja, stellt euch vor,
ihr habt kein Element und wollt etwas mit allen Elementen machen.
Was muss ich dann tun? Nichts. Richtig,
das also schon komplett. Ich muss also nur noch das
To-Do löschen. Sehr schön, das war ja einfach.
So, und dann gehen wir jetzt wieder in die
andere Klasse
So, das ist jetzt nicht ganz so einfach.
Was muss ich denn jetzt hier machen? Ich habe ja ein Element,
was ganz oben liegt und ich habe einen Stack von Elementen darunter.
Wie kann ich jetzt sagen, ich will mit allen Elementen was machen,
ja? Ja, ich muss mit dem Element machen.
Ja, das klingt schon mal gut. Also sage ich
mal, Consumer.de. Accept hieß die Methode. Er soll
jetzt also akzeptieren, wie heißt denn das oberste Element?
Heat heißt das, genau. Und dann? Und da
muss ich bei Mail-Points auch. Richtig, sehr gut,
ne? Den Consumer leite ich jetzt so einfach weiter an
den Stack unter mir. Ups, da habe ich das
eh klein geschrieben, das ist wegen einer anderen Sprache
So, sehr gut. Jetzt haben wir schon eine
ganz gute Grundlage, um dieses Tour String zu implementieren.
Das Tour String habe ich jetzt hier, glaube ich,
gar nicht vorbereitet. Lass mich noch mal ganz kurz gucken.
Habe ich das vorbereitet? Nee, dann mache ich das
on the fly. Nee, brauche ich gar nicht.
Das wird ja von Object geerbt. Das heißt,
ich kann das eigentlich direkt hier implementieren, also Otto String.
So, und ich finde es ganz nett,
wenn der leere String einfach zum Beispiel so dargestellt wird.
Okay, schweift der Klammer auf, geschweifte Klammer zu,
das ist der leere Stack.
Und dann machen wir das mal hier.
So, wie machen wir das jetzt hier
Vielleicht benutzen wir uns einfach mal unser Foreach. Oder ich mache mal
so, ich fange erst mal an mit einem String-Builder.
Ich will mir ja irgendwie einen String zusammenbauen, okay?
Und der String-Builder soll anfangen mit so einer Geschweifenkammer auf,
auf jeden Fall. Und jetzt will ich über mich selber
rüber iterieren, nämlich jetzt mal dieses Foreach.
Und jetzt muss ich ja so einen Konsumenten angeben.
Was kriegt er denn entgegen? Ihr kriegt ja
irgendein Element und gehe ich jetzt mal elen.
So, und jetzt kann ich irgendwas mit diesem Element machen.
Ich will das natürlich an diesen String-Builder, dem ich noch
gar keinen Namen gegeben habe.
Da will ich das jetzt ranhängen. Also sage ich lieber String-Builder,
tu doch mal das Element hinten dran und dann tu doch vielleicht
noch mal wie ein Komma und eine Leertaste hinten dran.
Gut. Okay. So, und dann kann ich es machen.
Und am Ende muss natürlich noch die geschweifte Klammer zukommen.
Also tu doch mal eine geschweifte Klammer zu am Ende hin.
Und jetzt seht ihr gleich einen klassischen Off-by-One-Error. Jetzt können
wir endlich das hier mal ausführen. Jetzt werdet ihr also
sehen, dass ich einen ganz blöden Anfängerfehler gemacht habe.
Sieht jemand den Fehler? Ja. Ja, das kommen
am Ende ist irgendwie blöd. Das muss ich jetzt auch wegkriegen.
Das ist ganz einfach, da sage ich mal,
die Länge des Stringbuilders soll die Länge minus zwei sein.
Dann löscht einfach die letzten beiden Zeichen
Und dann sieht es ganz gut aus. Und was man jetzt sieht,
was ich ganz spannend finde, ist folgendes, nochmal als,
ja, Sicherstellung, dass wir nicht verrückt geworden sind.
Das A, was ich ja hier initialisiert habe mit dem leeren Stack,
ist auch nach wie vor der leere Stack.
Das heißt also, dieses Push auf dem A lässt das A völlig unverändert.
A, bleibt so, wie es vorher auch schon war.
Und auch das Push auf dem B verändert nicht das B. Okay.
Gut. Okay, jetzt schaue ich mal ganz kurz.
Ich hatte hier noch was vorbereitet,
was schon implementiert war
Dieses, ich fange mit dem Leon Zeck an und pushe
was drauf und dann pushe ich noch mal was drauf und
dann pushe ich noch mal was auf.
Das macht nicht wirklich Spaß. Deswegen habe ich mal eine kleine Hilfsmethode gebaut,
der man beliebig viele Elemente übergeben kann und die dann selber dieses Push,
Push, Push, Push, Push, Push macht.
Damit man uns einfach ein bisschen netter aufrufen kann.
Jetzt gehe ich mal wieder zu meinem ursprünglichen Beispiel zurück.
Und ich denke mal, das können wir jetzt auskommentieren,
das brauchen wir nicht mehr. Und jetzt sieht man hoffentlich
mal was Sinnvolles. Ja, da haben wir es auch
schon. Man sieht jetzt also hier unten, wunderbar,
sämtliche Videos, die ich mir in naher Zukunft angucken will.
Und man sieht auch, wie groß die sind,
414 Megabyte, 2443 Megabyte, 426 Megabyte und so weiter.
Gut. Jetzt interessiert mich natürlich, wie viel Megawett brauche
ich denn insgesamt? Also nehmen wir an,
ich fahre in Urlaub, habe vielleicht irgendeinen,
weiß ich nicht, vier Gigabyte-Stick oder so.
Reicht der aus? Also ich will sozusagen sämtliche Größen,
die diese Videos haben, aufaddieren.
Und ich will das nicht machen mit irgendeiner Vorschleife oder so,
sondern ich will es ein bisschen funktionaler sozusagen machen.
Also gehe ich mal ganz kurz wieder auf meine Folien.
So, und das ist jetzt die Folien, in der
ich am längsten gearbeitet habe. Und es ist immer so,
je länger man an der Folie arbeitet, desto unverständlicher wird es.
Ich hoffe, ich kann sie trotzdem erklären. Also,
es gibt eine Operation bei solchen funktionalen Datenstrukturen,
die nennt sich Falten oder Reduzieren.
Ich habe sie hier mal an dem Falten orientiert
Was bedeutet es, wenn ich eine Sammlung von Elementen habe und
ich möchte sie falten? Das bedeutet so viel wie,
ich möchte sie irgendwie auf ein einziges Element oder auf
eine Zahl oder auf irgendwas Einzigartiges runterschrumpfen.
Und in diesem Fall will ich ja eigentlich nur diese ganzen Größen addieren.
Wie kann ich das jetzt schrittweise machen, eigentlich ganz einfach?
Ich fange mal an mit der Zahl null. Das hättet
ihr wahrscheinlich auch intuitiv gemacht, wenn ihr irgendwelche Kontoseilen addiert,
habt ihr erstmal eine Variable, wo die Zahlen null drinsteht.
Das ist jetzt nichts Besonderes. Wir fangen also mal an
mit der Null und dann schauen wir uns mal das erste Element an.
Das erste Element ist dieses Video, das ist das,
was ganz unten in dem Stack liegt
Aus diesem Video extrahiere ich mir jetzt die Länge.
231. Und dann addiere ich sie auf diese Null, die ich hier habe.
Ja, und dann wird hier diese 231 geliefert.
Also hier steht eigentlich nur 231 plus null ist 231.
Das kriege ich mir im Kopf gerade noch hin, oder? Gut,
und jetzt, das Interessante ist, dass ich mir jetzt,
dass ich das Gleiche jetzt mal kann mit dem nächsten Element.
Also ich schaue mir das nächste Element an,
extrahiere mir hier diese Größe und kann sie jetzt sozusagen mit
dem Zwischenergebnis von vorher zusammen addieren und kriege ein neues Zwischenergebnis.
Jetzt wird also zum ersten Mal interessant,
ich addiere also diese beiden Zahlen,
bekommen wir dann 7007 und das ist jetzt der Input in den nächsten,
in den nächsten Schritt sozusagen.
Da wird also jetzt
Mit diesem Zwischenergebnis, und hier kommt ein neues Zwischenergebnis raus,
und dann wird zuletzt noch 423 mit diesem Zwischenergebnis addiert,
und dann kommt das Endergebnis raus.
So kann ich also nach und nach aus einer beliebig langen Liste von Videos
mir die Gesamtlänge sozusagen rausholen, oder die sind die Gesamtgröße.
Okay. Man kann mit Foldright noch ganz viele andere Dinge
tun, aber das ist jetzt das erste Anwendungsbeispiel.
Und wie würde das jetzt in der Benutzung aussehen,
in der Benutzung muss sich sozusagen das, was ich hier blau gemacht habe,
das muss ich als Parameter an dieses Foldright übergeben.
Ich muss also dem Foldright irgendwie klarmachen,
wie sollst du denn jetzt aus einem Video und einem Zwischenergebnis
ein neues Zwischenergebnis machen
Das sieht man jetzt also hier. Es wird ein Video reingereicht.
Es wird ein Zwischenergebnis reingereicht. Und was berechne ich dann?
Ich berechne dann die Größe des Videos plus das letzte Zwischenergebnis.
Das soll also das Neue Zwischenergebnis sein. Das ist
nur so ein bisschen Mathematik. Und das zweite Argument,
was ich an Fotohalt übergebe, ist sozusagen der Startwert.
Das ist diese Null. Das macht in unserem Fall
nicht wirklich Sinn, da was anderes zu übergeben.
Aber stellt euch, ihr wollt die Größen zum Beispiel multiplizieren,
dann würde man da eine Eins übergeben.
Also man muss sozusagen das neutrale Element der jeweiligen Operation da
übergeben.
Ich weiß nicht, wie anschaulich diese Grafik ist.
Es gibt auch Leute, die können irgendwie besser in mathematischen Formen denken.
Deswegen habe ich das mal versucht, hier mathematisch hinzuschreiben.
Also stellt euch vor, ihr habt einen Stack,
der besteht aus A, B und C. Und ihr wollt
den jetzt falten mit einer bestimmten Funktion F und Z soll der Startwert sein,
dann ist das nur eine Kurzschreibweise für folgenden Ausdruck.
Ich muss also zuerst F von C und Z berechnen.
Das ist also quasi diese Schritte, wenn man so will.
Da kommt dann ein Zwischenergebnis raus und dann muss ich
dieses Zwischenergebnis reinstecken in F von B und dem.
Und dann muss ich dieses Zwischenergebnis reinstecken in F von A und dem.
Das ist also einfach nur eine geschachtelte Funktionsanwendung sozusagen.
Mehr steckt da überhaupt nicht dahinter. Das sind ja
so zwei Möglichkeiten darzustellen, wie man das machen kann
Gut. Das heißt, vielleicht bauen wir erst mal ganz kurz
die Klientensicht. Das hatte ich auch schon irgendwo vorbereitet.
Hier habe ich es vorbereitet. Ich möchte also die Größe
sehen. Jetzt können wir jetzt zum ersten Mal diesen,
dieses rein, das muss also noch implementiert werden.
Hier muss das jetzt implementiert werden.
Also ich muss also auf meinen Videos aufrufen, Fold.
Ride, ich muss jetzt eigentlich nur von der Folie abschreiben.
Also ich kriege also ein Video und ich kriege einen Akkumulator
und dann sage ich auf dem Video,
das brauche ich auf dem Video und das versteht er jetzt,
sondern nicht,
weil es die Funktion wahrscheinlich nicht gibt
So, und ich habe jetzt einfach nur das geschrieben,
was auf der Folie steht. Das Problem ist natürlich,
es gibt dieses Foldright noch gar nicht.
Das muss ich jetzt implementieren. Also gehe ich noch mal
ganz kurz zu meinem Stack zurück. Da ist es. So,
das sieht erstmal ein bisschen komisch aus, weil ihr vielleicht
zum ersten Mal seht, wie ihr Funktionen tatsächlich übergeben werden.
Also, was erwartet denn dieses Vorbereit? Das erwartet ja
diese blaue Funktion, die ich ihm erklärt habe.
Also einmal, das Video muss irgendwie reingesteckt werden in diese Funktion.
Dann muss der Akkumulator reingesteckt werden in die Funktion.
Und ein Wert vom selben Typ des Akmulators kommt auch wieder
raus aus der Funktion. Das heißt einfach nur,
diese Syntax, diese Funktion wird also übergeben.
Und dann wird hier
der Startwert übergeben
Okay, und was kommt zurück? Das Endergebnis,
was eben auch denselben Typ war, wie eben dieses Zwischenergebnis.
Gut. Jetzt implementieren wir das wieder. Das ist wieder
am einfachsten, wenn ich das bei Mill mache.
So. Also stellt euch vor, ihr habt überhaupt keine Videos.
Wie groß ist denn die Gesamtgröße? Natürlich null. Also
das ist dann einfach der Startwert, der da reingetan wird.
Also kann ich hier den Akku einfach zurückliefern.
Sehr gut. Bei dem Cons wird es wieder interessant und rekursiv
So. Da könnt ihr vielleicht mal einmal ganz kurz zehn Sekunden drüber nachdenken.
Ich habe das mir ja zum Glück ausgedruckt. Wo habe
ich es ausgedruckt? Ja, da habe ich es.
Sehr gut. Also ich muss jetzt eigentlich nur die mathematische Formel,
die ich gerade auf der Folie hatte, in Java übersetzen.
Ich mache das jetzt einfach mal für euch. Das ist ja
schon spät. Ich sage so diese Funktion, hey,
ich möchte dich anwenden. Zuerst auf den Kopf. Und
das zweite Ergebnis kriege ich ja erst durch die rekursive Anwendung,
indem ich die Funktion hups und den Akku wieder durchreiche an
den Stack unter mir.
Da habe ich
mich vertippt
So. Bringt das jetzt irgendwas? Wir führen das Programm nochmal aus.
Aha. Sehr gut. Also mein 4 Gigabyte Stick ist groß genug.
Die Videos sind insgesamt nur 3.8044 mega weit groß.
Gut. Okay, habt ihr bis hierhin erstmal Fragen?
Ich bin immer unruhig, wenn überhaupt gar keine Fragen kommen.
Entweder heißt das, es ist völlig klar,
was ich erzähle, oder ist es überhaupt nicht klar?
Wir haben jetzt also eine Funktion gebaut, die,
wenn ich hier einen Startwert gebe und eine Kombinationsfunktion,
mein Stack reduzieren kann oder irgendwie verarbeiten kann,
zu einem Endergebnis
Ja. Gut, keine Fragen dazu. Dann schauen wir mal,
wie es weitergeht. Wir haben noch fünf Minuten ungefähr.
Dann stellt euch vor, ich habe einen USB-Stick aus den 90ern,
der ist nur 250 Megabyte groß. Ich möchte wissen,
gibt es irgendwelche Videos, die klein genug sind,
damit sie auf diesen 250 Megabyte-USB-Stick raufpassen?
So. Und dazu habe ich natürlich auch wieder eine Folie.
Ich möchte jetzt
sowas machen
Ich möchte durch meinen Stack durchgehen und ich gucke mir jetzt die Elementweise an und sage,
ha, dieses Video ist ja klein genug,
das ist ein grünes Element, das will ich behalten.
Das soll, also behalten wir,
das tue ich mir immer hier unten, tue ich mir mal rein,
dann schaue ich mir das nächste Video an, ah,
das ist zu groß, das kann ich leider nicht übernehmen,
das muss ich mal gedanklich, vielleicht,
wenn ich später mal einen größten USB-Stick habe,
kann ich es mir vielleicht mal angucken.
Gut, nächstes Element. Ah, das ist wieder
klein genug, das kann ich mir wieder runterziehen.
Also hier geht es jetzt gar nicht um die Summe,
sondern nur um die einzelnen Größen.
Und dieses ist leider wieder ein bisschen zu groß. Gut,
das heißt also, ich habe eine Anzahl von Elementen
und möchte mir jetzt nur bestimmte Elemente daraus rauspicken.
Und so eine Funktion, die man dafür gerne schreibt,
ist die sogenannte Filterfunktion.
Das ist jetzt also hier wieder die Anwendung,
die Klientensicht
Ich sage also dem ganzen Tag, pass auf,
ich brauche nur gewisse Elemente von dir.
Und wie kannst du jetzt entscheiden, ob ich das Element haben will oder nicht?
Dafür gebe ich dir diese Funktion, die für ein Video entscheidet,
ob die Größe eben einen gewissen Wert nicht überschreitet.
Ich könnte auch völlig andere Dinge. Ich könnte zum Beispiel sagen,
ich schaue mir an,
ob der Titel eine Leertaste entfällt oder ich schaue mir an,
ob der Titel länger ist als zehn Zeichen oder irgendwas anderes.
Also ein beliebiges Kriterium, was ich auf Videos anwenden kann.
Und dieses Filter, ich mache hier wieder in meinen Stack gehe,
jetzt implementiere ich mal zuerst,
bevor wir es benutzen
Dieses Filter kann ich aus dem Fold Wright zusammenbauen.
Das ist vielleicht gar nicht so offensichtlich. Wir schauen uns
das mal ganz kurz an. Ich habe ja schon gesagt,
ich fange jetzt, womit fängt man jetzt hier an,
wenn ich noch gar nicht weiß, welche Videos ich habe?
Ich fange einfach mal mit dem leeren Stack an.
Okay, ich fange mit dem leeren Stack an.
Schaue mir jetzt jedes Element an und wenn es ein gutes Element ist,
dann tue ich es zu meinem akkumulierten Stack sozusagen dazu.
Und wenn es ein schlechtes ist, dann behalte ich einfach
mein aktuelles Zwischenergebnis. Das kann ich jetzt also implementieren,
damit das nicht zu lange dauert.
Schaue ich mal wieder, wie ich das möglichst kompakt
machen kann
Ah, wir haben das schon. Da kann ich jetzt
also das Fold Wright auf mir selber aufrufen.
Ich muss jetzt sagen, wie will ich jetzt kombinieren?
Was bekomme ich denn da? Ich bekomme ja ein Element
rein. Und ja, ich bekomme irgendeinen Akku rein.
So. Und jetzt muss ich schauen, ist das ein gutes Element oder nicht?
Dafür habe ich jetzt hier dieses Prädikat übergeben bekommen,
was Good heißt. Also frage ich doch jetzt mal,
dieses Prädikat teste doch mal das Element.
Kennt jemand dieses Synthesis mit dem Fragezeichen, Doppelpunkt?
Das ist einfach nur ein IFLs auf Austritt eben.
Ich frage, also ist es ein gutes Element,
wenn es ein gutes Element ist,
was mache ich dann
Dann pushe ich es auf meinen bisherigen Stack drauf.
Und ansonsten behalte ich einfach den bisherigen Stack.
So, was will er denn jetzt nicht akzeptieren?
Habe ich mich da irgendwo vertippt? Musst du die kennenlernen,
die lassen das erst mal definieren. Nee,
das ist ja die Funktion, die ich dann fortschreite.
Ah, ich habe vergessen, den Startschuss zu übergeben.
Ich muss ja sagen, er muss mit einem leeren
Stack anfangen
Ja, das tue ich meine eigene Zeile, dass ihr
die ganze Zeile seht. Also ich sage jetzt dem Fold
Ride, ich mache das hier mal ein bisschen kleiner.
Fang mit einem leeren Stack an, ja,
und dann schau dir jeweils jedes einzelne Element an,
wenn es ein gutes Element ist, dann füge es hinzu.
Ansonsten lass den Stack so, wie er ist.
Das bedeutet das, okay? Und dann müsste das
auch schon funktionieren, was ich hier gerade implementiert habe.
Ah ne, das muss ich ja noch,
das muss ich ja noch aufrufen.
Also, ich muss jetzt filtern. Das schreibe ich einfach
wieder von der Folie sozusagen ab. Das Video,
was reingesteckt wird, da gucke ich mir an,
wie groß ist es denn?
Und es muss kleiner gleich diese Maximalgröße sein.
Das ist jetzt also die Benutzung dieser Filterfunktion
Gut, das schauen wir uns noch mal ganz kurz an.
Er behauptet also, genau diese drei Videos sind kleiner gleich 250 gewesen.
Sieht eigentlich ganz gut aus, oder? So,
und jetzt, ich bin froh,
dass ich noch bis zu diesem Punkt gekommen bin.
Denn eigentlich geht es ja hier auch heute ein bisschen um
das Vertragsmodell. Nochmal Frage an euch. Was ist denn
der Vertrag dieser Filterfunktion? Kann ich da irgendwas zusichern?
Kann ich da irgendwas entschoren? Ich hatte am Anfang relativ
viele Elemente und ich wollte mir nur die guten rauspicken.
Kann man da irgendwas sagen über das Ergebnis,
was da rauskommt
Penny. Schon zu spät. Wie viele Elemente könnte es denn sein?
Also wie mit zehn Elementen anfangen können dann da 20 rauskommen?
Ja. Ja, das ist mir nicht,
aber wir müssen das so dieses ganze Stack übergeben, oder?
Also zurück. Die Tatsache, dass ein Stack zurückkommt,
sieht man ja hier schon beim Rückgabeltyp.
Das ist also... Ach so, diese Neil-Diskussion,
wie gesagt, möchte ich an der Stelle nicht für.
Die führt immer so ein bisschen weg von dem,
was man eigentlich sagen will. Wie groß ist der Stack,
der da zurückkommt im Vergleich mit dem Stack,
auf dem ich es ausgeführt habe?
Ja. Ich habe es leider akustisch nicht verstanden. Muss
größer gleich null sein. Das ist ja sozusagen schon zugesichert,
weil das eine Nachbedingung von der Size-Methode ist.
Das ist also nicht
Sehr gut. Das kann also, der Stack kann nicht wachsen.
Das kann man damit sagen. Ich kann das so sagen
. Die Größe von dem Stack, der da zurückkommt,
kann höchstens so groß sein wie meine eigene Größe.
Es können nicht mehr Elemente werden. Wenn ich mir
nur die Guten raussuche, kann es nicht mehr werden.
Man kann sogar noch mehr zu sichern. Ich kann sagen,
und leider haben wir keine Zeit mehr, diese Funktion zu implementieren.
Aber wenn es diese Funktion gäbe, die prüft,
ob alle Elemente folgendes Prädikat erfüllen, könnte ich sagen,
oh, das Result fehlt da noch.
Dann könnte ich sagen, pass auf,
in dem Stack, der zurückkommt,
sind nur noch gute Elemente drin
Und nicht zum Beispiel die Schlechten. Manchmal verwechselt man das,
was ein Prädikat, was ein Prädikat aussagen will.
Okay, ich bin jetzt ungefähr bei 40 Prozent von
dem Stoff, den ich heute machen wollte.
Hm, schade. Ich wollte euch noch nützliche Dinge in Java 8
zeigen und was ist Neues auf Collections alles gibt und so.
Vielleicht finden wir dafür nochmal irgendwie Zeit. Aber ich will
euch nicht irgendwie überstrapazieren. Soweit sind wir also bis jetzt
gekommen und das nächste, das hatten wir auch schon.
Nee, das haben wir noch nicht gemacht. Ja,
das lade ich natürlich auch. Das ist ein bisschen schade,
weil ich wollte jetzt, nachdem es ein bisschen theoretisch war,
nochmal sehr praktisch werden und euch zeigen,
wie ihr den Code in der SE2 deutlich schöner formulieren könnt Schade,
hatte ich jetzt leider keine Zeit mehr für.
Vielleicht können wir das noch irgendwie organisieren,
dass ich das noch machen kann. Okay, dann,
ich lade das alles hoch und bedanke mich bei euch
wie immer für eure Aufmerksamkeit