Ja, guten Tag, bitte Damen und Herren. Freue mich,
dass Sie alle wieder da sind, obwohl das letzte Mal die Vorlesung ausgefallen ist.
Tut mir leid, ich bin am Freitag nach der letzten Vorlesung,
wo ich hier sofort mit Blaulicht ins Krankenhaus gebracht worden bin.
Und dann habe ich dann über eine Woche gelegen.
Und dummerweise Dr. Sepp gewollt mich vertreten und hat sich mundtappbar auf den Weg gemacht,
die wir ja letzten Freitag, wollt ihr Vorlesung halten,
hat auf dem Glatteis mit seinem Fahrrad einen Unfall gebaut,
musste auch zum Arzt.
Und damit hatten wir leider keine weitere Backup-Lösung mehr.
Das bringt mich jetzt organisatorisch zum kleinen Problem, weil Das Aufgabenblatt,
was meine Übungshelfer dann am Dienstag am letzten Freitag verteilt hatten,
das braucht Stoffe heute erst in der Vorlesung dran ist.
Und wir haben uns jetzt Folgendes überlegt beim Aufgabenblatt 4,
was jetzt schon verteilt ist, die Teilaufgabe 3 vom Aufgabenblatt 4,
die brauchen sie erst Montag in einer Woche abgeben.
Weil das ist der Stoff, den wir heute in der
Vorlesung besprechen und dass wir ein bisschen unfair zu erwarten,
dass sie am Wochenende sich hinsetzen und darüber grübeln,
wenn es am Montag abgegeben sein soll.
Das Aufgabenblatt Fünf, was dann in einer Woche abgegeben werden sollte,
das werden wir heute schon verteilen. Sie können es vorbesprechen in den Übungen,
schon die konzeptuellen Überlegungen, was ist eigentlich der Sinn der Aufgabe,
was ist zu tun in deinen Übungen, besprechen,
aber sie haben dann 14 Tage Zeit, das zu besprechen,
zu lösen und geben das Aufgabenblatt 5 dann erst in zwei Wochen ab ihre Lösung.
Das ist also der Plan, damit sie mit der Vorlesung
wieder in den Zack kommen mit dem Stoff,
der in den Übungen gefordert wird
Noch ein weiterer Punkt. Ich werde bei dem,
was wir heute besprechen, einfach ein paar Teile auslassen,
so damit wir besser wieder in Gang kommen und
die Lücken füllen können und gegen Ende da an hinkommen,
wo wir wollen.
Also was ich auslassen werde, ist das genaue Besprechen von Strings.
Ich mag es ohnehin nicht, wenn daran String statt Symbole benutzt werden.
Manchmal schmuggeln meine Übungshelfer in die Aufgabenblätter und ich übersehe es
rein, das mit Strings gelöst werden soll.
Also beim Aufgabenblatt 4 bin ich voll oft zufrieden,
wenn sie alles mit Symbolen lösen und nicht mit Strings.
Wir werden es dringend
auch nicht weiter diskutieren
Was ich auch nicht besprechen werde,
obwohl das eigentlich sehr schade ist und schick ist, ist,
dass wir das Einbauen von Testfällen und Ähnliches genau besprechen.
Das kann aber dann in der Woche drauf in den Übungen nachgeholt werden,
weil sie ja keinen Übungsplatz zu besprechen gibt.
Also das über Testfälle sollten wir bei der Gelegenheit dann sprechen.
So, gucken, wann dann? Oh, ja.
Waren? Das ist so ungefähr die Stelle,
denke ich, wo wir das letzte Mal aufgehört hatten.
Ich gehe nochmal
Hierhin zurück. Wir hatten drüber gesprochen,
wie man mit Assoziationslisten arbeitet und gleich hat es Prädikate.
Ja, vielleicht nochmal. Ja, das will er nicht
. Das ist der Teil, den ich überspringen möchte.
Genau, ich glaube, das hier war die letzte Folie,
die wir behandelt hatten.
Wie können wir eigentlich Datenobjekte darstellen, indem wir Funktionen
dafür nehmen
Wir hatten eine Begleitheitsprädikate gesprochen. Dann mache ich nochmal an der Stelle weiter. Also die Funktion,
was so, wo mich das System im Moment nicht die Folie finden lässt,
an der es nochmal erklären wollte,
war ja dazu da, dass wir in Listen,
die immer einen Schlüssel und einen Wert enthalten,
einfach suchen können und das Element in der Liste finden,
das erste Element, bei deren erstes Element genau mit diesem Schlüssel übereinstimmt.
Und wir hatten auch gesehen, damit wir sagen können,
wie verglichen wird,
gibt es einfach
verschiedene Funktionsnamen
In Assak, als Kuh, als Wie,
die mit unterschiedlichen Versionen von IQ, EQV oder Iqual vergleichen.
Und damit den Grad der Gleichheit unterschiedlich bestimmen, ja.
Also ich möchte bitten, dass sie das Aufschreiben und Erzeugen von Assoziationslisten
sehr gut üben und auch die Art, wie man darauf zugreift.
Und dabei berücksichtigen, dass es zwei Atem gibt. Also die einen lieben es,
dass mit zwei elementigen Listen zu machen,
die anderen lieben es, das mit Tuppeln zu machen,
indem man einfach zwei Werte nimmt und sie mit Cons verbindet,
zu einem gepunkteten Paar oder dotted per
Je nachdem, was sie in der Liste haben,
zwei elementige Listen oder gepunktete Paare. Müssen sie
um den assoziierten Wert rauszuholen, unterschiedliche Funktionen nehmen.
In dem einen Fall können sie einfach mit Kütter zugreifen,
auf das zweite Element des Paares.
Im anderen Fall brauchen sie das erste Element der Restliste.
Und das wäre dann die Funktion Cutter. Oder die Funktion Second.
Also das schauen sich bitte ganz genau an. Nicht,
weil ich das für so unbedingt die wichtigste und weltbewegende Art finde, in Listen zu lösen.
Aber weil wir schlichtweg in der Klausur steht, und ständig,
wenn es darauf ankommt, dass man irgendwelche Datenbestände besucht,
durchsucht dieses Modell mit den Assoziationslisten wählen.
Ja, das lohnt sich einfach in der Klausur fit zu sein,
sagen zu können, wo finde ich in einer Assoziationsliste, welchen Wert.
Na denn, dann verlieren sie damit nicht allzu viel Zeit.
Ich habe bei den Übungen, die abgegeben wurden,
besonders in meiner Übungsgruppe gesehen. Wenn man aus einer Welt von,
der Punkt der Imperativen Programmierung kommt,
für das Durchsuchen von Tabellen durchaus andere Datenstrukturen beliebt sind und auch sinnvoll sind,
also Ein paar Teams haben gesagt, Moment,
ich brauche den zehnten Buchstaben aus dem Alphabet,
dann mache ich doch ein Eric und greife da rein und bildet mir das Element 10.
Was soll ich es lange suchen? Gut gedacht. Reckett
hat auch solche Strukturen wie eben Vektoren, mit denen wir
genau ein Eric bekommen. Und wenn man sich ausgerechnet,
welches Element ich brauche, dann greife ich da rein.
Hat den Vorteil, der Zugriff ist unabhängig von der Länge der Tabelle.
Ja, eine andere Art, wo es schön,
unabhängig von der Länge der Tabelle ist,
wenn man eine Hash-Tabelle nimmt,
gibt es in Recket auch
Ich würde mir trotzdem wünschen, dass sie diese Assoziationslisten üben,
einfach weil wir die später brauchen und sie daran und
wir damit die Rekursion über Listen abkürzen können.
Aus der funktionalen Sicht sind Tabellen wie Viktoren und so,
ja, die laden immer dazu ein,
dass man mit einmal imperativ wird und einfach ein Element drin ändert.
Es gibt wenig elegante Arten, eine Rekursion über einen Vector zu machen.
Und dann ist man gleich in der Welt der imperativen Programmierung.
Schauen wir uns jetzt hier nochmal in diesem Beispiel an,
wie das denn mit der Rekursion über Listen ist
Ist, also die einfachste Rekursion,
wenn du eine Liste abarbeiten wollen und mit jedem Element der Liste etwas tun wollen,
dann nehmen wir die rekursive Definition einer Liste und sagen,
okay, wenn meine Lehre,
wenn ich eine Länge der Liste haben will,
und sie ist leer, testen mit Empty oder Noll,
dann sagen wir, das Resultat ist null.
Und wenn sie nicht leer ist, dann ist sie so lang
wie der Rest der Liste plus eins für das erste Element,
was wir weggenommen haben.
Und wir können also unsere eigene Längsfunktion jetzt über eine Liste
sehr schön reklusiv schreiben
Eine Länge einer Liste, wenn das Ding leer ist,
ist null und in allen anderen Fällen addiere ich eins zu der Länge
und wir haben damit eine simple lineare Iteration über eine Liste.
Und über die Art, welche Art von Rekursion,
welchen Aufwand verursacht werden wir noch ausführlich sprechen.
Aber wenn Sie über Listen arbeiten wollen,
dann nimmt man typischerweise in Recket nicht die Vorschleife,
sondern setzt tatsächlich so eine Rekursion auf oder nimmt die Mapping-Funktion,
über die wir noch sprechen werden.
Ähnliches Beispiel hatten wir hier mit der Member-Funktion und Ich wollte jetzt nochmal
ein paar Vereinbarungen, wie wir Variablen benennen, besprechen.
Also das ist ein typischer Konsens ist, wenn wir einfache Variablen meinen.
Also eine Struktur, die nicht listenartig ist,
dann nehmen wir einen Variablennamen, der nicht in S endet.
Wenn es eine Liste ist, die einfach geschachtelt ist,
eine lineare Liste, dann lassen wir den Namen mit S enden.
Und wenn wir andeuten wollen, wie stark die Schachtelungstiefe ist,
zum Beispiel Listen von Listen,
dann würden wir es nennen XS YZs mit 2S. So viel
S für die Schachtelungstiefe ist
Okay. Das hatten wir schon besprochen. Gehen wir jetzt
mal auf das Kapitel Datenstrukturen. Ich werde den Teil mit Strings
überspringen, können wir vielleicht gegen Ende Semesters noch nachholen.
Wenn wir bei Gelegenheit nochmal intensiver mit Strings arbeiten wollen.
Aber was ich Ihnen gern noch zeigen möchte,
sind einfach nur ein paar Funktionen, wenn Ihnen Dostrings begegnen.
Also wir können Strings auf Ihre ephabetische Sortierung vergleichen mit Stringless.
Ja, das entscheidet aufgrund der alphabetischen Sortierung.
Und wir können da auch mehrere Argumente reintun,
wie man es in Recket so gewohnt ist
Um dann für eine ganze Folge von Werten die Sortierung zu vergleichen.
Verbindet sie mit Appent, was ihnen noch begegnen kann,
wenn sie sie doch mal verwenden sollten.
Springs kommen in Record in zwei Ausprägungen.
Entweder sie sind konstanten, dann haben wir keine Chance,
sie irgendwie zu verändern, irgendwie ein Zeichen drin zu ändern.
Das können wir testen mit der Funktion immutable. Da sind wir aber
in der Stringkonstante hinschreiben, da wird sie so erzeugt,
dass man das Ding nie wieder ändern kann.
Und dann sagt uns immutable, also das müssen wir so nehmen,
wie es ist. Wenn wir dagegen Eins dringend erzeugen mit Mixed
drin und sagen, wir wollen die Länge fünf haben.
Und alles soll initialisiert werden mit dem Charakterpunkt. Ja,
dann sagt A Mutiple is false, also Mix dringend erzeugt uns dringend,
wo man tatsächlich mein Imperativ programmiert, Werte ändern kann.
Und man kann es tun mit String Set, indem man sagt,
an der zweiten Stelle, tu doch mal ein Lamm da rein.
Und dann haben wir es verändert. Aber wie gesagt,
das ist der Imperative Teil und die Beispiele zum inperativen Teil,
die lassen wir mal weg.
Können wir bei Gelegenheit mal in den Übungen besprechen.
In der Musterlösung für Und diese Funksprücheaufgabe,
die Sie gerade abgegeben haben, dann haben wir es so gemacht,
dass tatsächlich der Rechner anschließend ansagt, spricht,
was der Funksprung, Alpha Bravo, Charlie und so weiter.
Sie können in Racket, die Systemkommandos aufrufen, mit der System vermitschen.
Und für Rechner mit MacOS habe ich ein System MacOS-Mordul geschrieben,
was Sie dazuladen können.
Und das macht die Funktion say verfügbar,
die über Shelpremandos aufgerufen kann
Also der Shell-Kommando See auf dem Mac zum Beispiel,
dem sagt man einfach See und dann spricht der los.
Man sagt, welche Stimme es sein soll. Es kann
einen Audio-File erzeugen oder es spricht einfach. Und das,
wenn Sie das verwenden wollen, wenn Sie einen Mac haben,
würden Sie sagen, Require,
Record System und die Funktion kennt Speak, die testet,
ob der System Tide MacOS ist.
Und wenn das der Fall ist,
dann kann man eben die Funktion Say aufrufen und der Texte
sprechen lassen
Gut, Vektoren, das Gegenstück zur Ereys bekommen sie,
indem sie einfach sagen, Klammer auf, Vektor,
ein paar Argumente angeben, Klammer zu,
in Recket ist nicht der Zwang,
dass alle Elemente eines Weckers vom selben Typ sind.
Ja, meistens bietet es sich an, die Typ gleich zu machen,
da hat man weniger Abfragen, aber sie dürfen alles,
was sie benennen können als Wert, in einen Vektor reinschreiben.
Ja, und also wir können zum Beispiel hier hinschreiben,
es ist einfach konstant dennotieren, Hash,
Klammer auf und dann jetzt drei Elemente,
die null eine Liste und den String, Anna.
Gar kein Problem inrected. Das ist, die Elemente müssen
nicht typgleich sein. Wir können Vektoren konstruieren, indem wir
den Konstrukt der Vektor nehmen und dann da einfach die Argumente listen.
Wir können mit Milk Vektor sagen, wie Lampa sein soll und eine Initialisierungswert angeben.
Und wenn wir ein Element haben wollen aus dem Vektor,
nehmen wir die Funktion Vektorep und sagen,
an welcher Stelle wir zugreifen wollen.
Wir können einen gegebenen Vektor in eine Liste wandeln,
wenn wir eine Rekursion über Listen drüber machen wollen.
Ja, Vektor tolist und das gibt uns die Liste oder Liste
Vektor macht aus einer linearen Liste
einen Vektor
Und wir können fragen, ob etwas ein Vektor ist.
Ist ja manchmal auch nicht schlecht. Und das ist einfach
weg, damit ein Fragezeichen. Gut. Die andere Datenstruktur,
die man noch kennen sollte, das sind Destructures,
das entspricht den Records, die man aus anderen Programmiersprachen kennt.
Ja, das ist ein Datentyp,
bei dem wir eine Einheit haben,
mit einer festen Zahl von Feldern und diese Felder können unterschiedlichen Typs sein,
in funktionalen Programmiersprachen, denn die ist practisches so,
dass man einen Konstrukte hat und automatisch Zugriffsfunktionen für die Felder
erzeugt werden
Also wir können eine Straktische erzeugen mit die Feinstrakt,
geben ihm diesem Typ einen Namen.
Und dann machen wir eine Nüste und da führen wir einfach
die Feldnamen auf und machen dann eine Klammer wieder zu.
Zum Beispiel ist Stracture für eine Adresse,
die nur die Straßennamen und die Hausnummer haben soll,
würden wir sagen, die Feinstruct.
Da ein Typ ist Adress und Street und Number.
Und dann existiert dieser Typ. Und das erzeugt automatisch erstmal
ein Typredikat. Wir können, also wenn wir etwas haben,
fragen, bist du eine Stracture vom Typ etwas?
Der Name des Typs, den wir gegeben haben,
mit einem Fragezeichen, dahinter ist der Name des Typredikats
Wir kriegen automatisch einen Make, einen Konstrukte,
der heißt Make und dann kommt der Typname.
Wir bekommen Selektoren für die Felder. Also der Typname und
dann der Name des Feldes und wir bekommen Modifikaturen für die Felder.
Wir können da die Werte ändern. Also wenn wir dieses Beispiel mal hier ausreizen,
die Finestruct, für einen Datentyp Bettris,
mit den Feldern Straße und Nummer,
dann können wir die Adresse des britischen Premierministers oder der mindesten Premierministerin erzeugen,
mit Make Etwas,
Downing Street und Ten
Und das erzeugt also ein solches Element.
Und wir können dann fragen, Attress, ist es eine,
ist es vom Typ Attress, dann kommt Truth zurück.
Wir bekommen Primeminister Attress zurück mit Downing Street.
Wir bekommen die Hausnummer zurück mit Attress Number. Und wenn
der jetzt aus dem Amt ausscheidet und Cito anders hin,
dann können wir Set, Attress,
Street sagen und setzen die jetzt auf den Strand und
die Hausnummer drei für den Strand
zum Beispiel
Also so viel noch ganz kurz zu den Datenstrukturen,
die wir in Reckett auch haben. Wir werden in dieser
Vorlesung aber wirklich im Wesentlichen mit Listen arbeiten, ja.
Nur wenn ihnen die anderen mal begegnen, in irgendwelchen Beispielen,
dass sie sie lesen können. Das wichtigste Thema heute und das,
was sie für die Aufgabe 3 auf dem Aufgabenblatt 4 brauchen,
das ist das Thema Semantik und Korrektheit.
Also und ich möchte bei der Gelegenheit nochmal an ein paar
Grundbegriffe aus der Programmiersprachenwelt erinnern. Die sollten sie kennen,
die sehen nur aus der funktionalen Sicht vielleicht ein bisschen anders aus.
Und dann sagen, darüber reden, was es bedeutet,
wenn wir Einen Ausdruck in Reckett schreiben.
Wie findet Reckett den Wert? Und wie können wir uns
mal dem Rechner drüber einigen, was der Wert sein soll?
Das betrifft einmal den Begriff Wertesemantik und dann die Reduktionsmodelle,
um einen Ausdruck auszuwerten und zu sagen, ja,
hurra, der Wert von diesem Ausdruck ist drei.
Und eine Kluft, die wäre eigentlich bei jeder Semantik haben,
ist, dass wir denotationell für Programmiersprachen immer definieren, was es bedeuten soll.
Und meistens ist der Dinotationelle Teil mächtiger als das,
was man hinterher programmieren kann, um den Wert auch zu ermitteln.
Also von der Art, wie der Rechner versucht,
den Wert zu finden, kann es abhängen,
ob da eventuell zwei verschiedene Werte rauskommen.
Also ob es eindeutig ist oder ob es überhaupt gelingt,
den Wert zu berechnen. Das eine ist die operationale Semantik,
die beschreibt, was die Implementation der Programmiersprache denn versuchen wird,
um rauszukriegen, was dieses Programm bedeutet und demotitionell ist das,
was es bedeuten sollte, qua Vereinbarung über die Programmiersprache.
Und In den meisten Programmiersprachen klafft dazwischen leider eine Lücke.
Das trifft sie sehr hart in der Prolog-Welt,
das wird Wolfgang Menzel schon erklärt haben, dass wir da
eine Lücke klaffen haben zwischen der operationalen Semantik und der die natutionellen.
Und wir haben dasselbe Problem in Reggit. Auch da gibt es Dinge,
die wir aufschreiben können und definieren können, denatutionell hinschreiben.
Aber wo regnet in eine Endlosschleife gehen würde,
wenn es versucht, den Wert zu finden?
Genau. Also fangen wir erstmal damit mit den Grundbegriffen der Programmiersprache
nochmal an und überlegen uns, warum brauchen wir eigentlich Programmiersprachen?
Warum können wir es nicht, wie es am Mac oder
ein iPhone oder einem sonstigen System
Ist, kommt man es damit zufrieden geben, dass wir einfach sagen,
Siri, wie spät ist es oder aus natürlicher Sprache
wird alles gelöst, was wir haben wollen.
Siri löst mir die Hausaufgaben. Gut. Also Programmiersprachen sind zum einen,
dienen sie als Notationssystem für Algorithmen. Das ist das,
was sie kennengelernt haben in den ersten beiden Semestern,
wenn sie in Yava programmiert haben oder in anderen Programmen wir sprachen.
Wir nehmen die Programmiersprache, um aufzuschreiben, was der Rechner für uns tun soll.
Die andere Welt, die begonnen mit der funktionalen Programmierung.
Das ist, dass wir Programmiersprachen auch einfach verwenden, um ein Problem zu beschreiben.
Und damit nicht unbedingt gleich die Lösung beschreiben,
aber wir wollen vom Malt spezifizieren,
was das Problem ist, sodass man drüber nachdenken kann,
dass ein Programmtransformation vielleicht daraus eine Lösung entwickeln kann und so weiter.
So, ein Programmiersprachen gibt es wie Sand am Meer,
ich habe irgendwann aufgehört zu zählen.
Also unterscheiden sich vor allem bezüglich des Verarbeitungsmodells,
der hatten wir zu Anfang der Vorlesung schon mal daran erinnert,
ja,
funktional,
relational,
Stromorientiert. Und der Konzepte, die sie zur Verfügung stellen,
um unser Problem zu beschreiben. Und dann,
ganz wichtiger Unterschied, in der Syntax, wie schreibt man?
Meistens ist die Syntax nicht so das Wichtige,
die sorgt nur dafür, ob es interessant aussieht oder nicht.
Aber es ist meistens Zucker, der es uns leicht macht, es aufzuschreiben.
Also als ich Kobol gelernt hatte, war ich überrascht,
wie viele Möglichkeiten es gibt zu sagen, ich möchte addieren.
Und das ist in vielen anderen Programmiersprachen auch so,
dass man der Variationen hat, die allerdings nicht nötig sind.
In Recket haben wir gesehen, ist die Syntax unglaublich billig, Klammer auf,
Klammer zu und man beginnt mit dem Namen einer Funktion oder einer Special Form.
Und wir haben dafür aber die Möglichkeit, ein Racket eigengesonntags einzuführen,
mit die feinen Syntax, werden wir später noch sehen.
So, und dann die Pragmatik, bis verwendet wird,
in den Vorlesungen, die nach der heutigen kommen,
möchte ich vor allem über die Pragmatik reden.
Wie verwende ich das Ganze? Ja,
aber heute reden wir drüber, was bedeutet es?
Und dann gibt es Programmiersprachen für bestimmte Anwendungsgebiete. Ja,
warum sind wir noch nicht so weit,
dass man alles in natürlicher Sprache machen kann? Vermutlich,
weil Selbst die Menschen sich nicht ganz einig sind,
bei der formalen Definition, was ein Satz in natürlicher Sprache in ihrer Muttersprache bedeutet.
Es ist die natürliche Sprache, ist in der Regel sehr lebendig und entwickelt sich.
Das eine Generation, die nächste nicht versteht,
das bestimmte Gruppen und Klicken, was ganz anderes meinen.
Selbst beim letzten Aufgabenzettel habe ich gehört,
also eine Übungsgruppe von David Mostella, das Wort Schlüssel zur Verwirrung gesorgt hat,
weil wir einerseits Schlüssel verwenden, um den Schlüssel in eine,
Informationsliste zu bezeichnen, wenn wir eine Tabelle durchsuchen,
aber das Anwendungsgebet auch den Begriff Schlüssel hatte,
weil man einen Schlüssel für den Code brauchte,
der zu programmieren war.
Also dasselbe Wort in verschiedenen Bedeutungen und schon war die Aufgabenstellung unklar.
Einigen ist es nicht aufgefallen, wahrscheinlich,
weil sie den Zettel nicht so genau gelesen haben.
Aber die hatten dann Glück gehabt. Die andere Möglichkeit besteht
aber für die Mehrdeutigkeiten besteht in der Struktur.
Wenn grammatikalisch im selben Satz nicht klar ist,
was sich worauf bezieht
Also natürliche Sprache, so schön sie ist und ja,
der Hafen, in dem unsere ganzen kulturellen Erinnerungen beschrieben werden,
sie ist unsere Ziese,
ist nicht stabil in der Bedeutung und unglaublich aufwendig zu analysieren.
Ein berühmtes Beispiel aus der Zeit, wo begonnen wurde,
mit Computer natürliche Sprache zu bearbeiten und automatisch zu analysieren,
das Sprachsystem 1960, ja, über 50 Jahre jetzt schon her,
hat er als einen Testfall, time flies like in arow.
Ja, ein schöner Satz, man liest ihn,
jeder, der Englisch kann,
versteht sofort,
was gemeint ist,
So, aber was bedeutet das? Das Harvard-System hatte
insgesamt vier Bedeutungen geboten, was das alles heißen könnte.
Also die Zeit fliegt wie ein Pfeil, wobei Fliegen jetzt mehrdeutig ist,
ob das im übertragenen Sinne von Vergehen gemeint ist, ja,
die Zeit schwindet dahin oder eben tatsächlich fliegt wie ein Flugzeug.
Das Harvard-System hat gemeint, es heißt Fliegen, so richtig
wie ein Flugzeug fliegt. Kann aber auch sein, Timeflys,
like MRO, da ist Time als Wert genommen worden,
too time,
Stoppuhr drücken,
Stoppel die Zeit oder mette die Zeit von Fliegen,
die wie ein Pfeil aussehen, like and error.
Da steckt drin, wörtlich, ne? Oder messe so
wie ein Pfeil die Zeit von Fliegen. Warum? Also
dann bezieht sich das Like in error auf das Verb-Tagen.
Ja? Steckt auch drin. Und das Schönste,
was mir am besten gefällt, Zeit fliegen mögen einen Pfeil.
Margot mystische Zeit fliegen, Timeflies, diese mystischen Zeit fliegen,
mögen einen Pfeil. Ein simpler Satz, wenige Worte,
Menschen ist klar, was es gemeint ist,
mit all dem kulturellen Hintergrund,
der in unserer Sprache steckt
Das so zu beschreiben, dass ein Rechner versteht, was gemeint ist,
es ist unglaublich schwierig und hier sind eben sehr viele Verwechslungsmöglichkeiten drin.
Also wir haben erstens die lexikalischen Mehrdeutigkeiten, Time,
also einmal die Zeit oder Stoppe,
durch die Fleiß fliegen oder die fliegen selbst oder liked,
mögen gleich wie so wie.
Und dann eben die strukturellen Möglichkeiten, was ist Subjekt,
was ist Objekt, was gehört zueinander.
Und wenn man eine Programmiersprache einführt, dann bemüht man sich,
die so zu definieren, dass man diese Mehrdeutigkeiten nicht hat.
Das ist der Hauptgrund, weshalb man sie damit quält,
dass sie Programmiersprachen lernen sollen
Damit sie ein präzises Notationssystem für Algorithmen haben. Ein Beispiel,
was nur strukturelle Mehrdeutigkeiten hätten, was ich auch richtig schick finde,
ist, das klappt Deutsch wie Englisch.
I saw the men on the hill with the teleskop.
Das ist ursprünglich auf Englisch gesagt worden.
Klappt genauso gut auf Deutsch. Ich sah den Mann auf
dem Berg mit dem Fernenrohr. Wenn ich Ihnen das sage,
haben Sie bestimmt eine Vorstellung, was da so passiert ist.
Das gibt sehr viele Möglichkeiten, diesen Satz zu verstehen.
Du kannst dir schon mal die Frage stellen,
wo ist das Fernenrohr
Es könnte bedeuten, es gibt ja einen Hügel und auf
diesem Hügel steht ein Teleskop, eine Stammwarte.
Und da habe ich den Mann gesehen. Oder ich stand unten im Tal,
habe mein Teleskop genommen und habe da auf den Hügel geguckt,
dann ist das Teleskop bei mir.
Oder dieser Kerl, da ist ein Dieb,
der mir mein Teleskop geklaut hat und ich sah ihn mit
dem Teleskop und er ist irgendwo aber anders gewesen.
Freie Möglichkeiten, worauf sich das Wüste-Teleskop beziehen kann.
Auf dem Hügel mit dem Teleskop, ich sah den,
ich sah den Mann,
ich habe es in der Hand oder der Mann hatte das
Teleskop,
Also es ist ein kleines, tragbares oder ist es ein riesiges Teleskop?
Ist völlig unklar. Oder I saw the men,
das kann auch bedeuten, wir haben uns da oben getroffen irgendwo.
Wir haben uns offen dem Hügel getroffen, wo das Teleskop steht.
Ja, wer hat das Fernrohr?
Der Mann oder entweder keiner, sondern der Hügel.
Und wer war auf dem Berg? Ich, der Mann
oder beide. Keine der Fragen können wir eindeutig beantworten,
wenn uns jemand diesen Satz gesagt hat.
Mit diesem Beziehungsmehrdeutlichkeiten ist er
aus dem Kontext gerissen,
völlig unklar
Also eine natürliche Sprache ist eigentlich überhaupt nur zu verstehen,
wenn man die Sätze in einem Kontext sieht,
aber ein einfacher Befehl oder so ist meistens total unklar,
außer es ist eine Befehlssprache.
Und es gibt viele Situationen, wo Leute sich eine Befehlssprache
ausgedacht haben, damit eben der Kontext nicht nötig ist.
Aber man kann auch syntaktisch korrekte Texte haben,
die absolut nichts bedeuten, wenn die Worte,
die Symbole keine Bedeutung haben.
Wieder als Alice in Wanderland, der Zabberwoki.
Ja, also Programmiersprachen müssen präzise und eindeutig sein,
präzise Definitionen der Semantik
Damit wir, wenn wir einen Compiler schreiben oder einen Jobritter schreiben,
eindeutig wissen, was das Hingeschriebene bedeutet.
Und dass wir auch das Verhalten von Programmen eindeutig vorhersagen können.
Und wir beweisen können, ob unser Programm korrekt ist.
Gut, man kann eine Programmiersprache formal so definieren.
Ich habe das aus dem Buch Informatik von Bauer Ghost,
das ist schon recht alt, aber die Definition hält sich.
Also eine Programmiersprache ist über einen endlichen Alphabet gegeben.
Alphabet definieren wir hier als eine endliche Menge von Grundsymbolen
Und dann ist eine Programmiersprache zunächst mal eine formale Sprache,
eine formale Sprache, also eine Teilmenge aus Sigmar Stern,
all den Worten, die wir über diesem Alphabet erzeugen können.
Und dann brauchen wir eine Vorschrift,
die jetzt in taktisch richtigen Programmen eine Bedeutung zuordnet.
Und außerdem in den User Guide steht dann meistens noch eine Vereinbarung zur
richtigen Verbindung der Sprache, als wie man die sinnvoll benutzt.
Und diese Semantik, das muss eine berechenbare Funktion sein.
Also wenn wir uns eine Semantik ausdenken,
die nicht berechenbar ist, dann können die Leute viel schreiben,
aber wir können die Programme
nicht ausführen
Gut, häufig ist es so, dass die Semantik in
den Programmierhandbüchern nicht formal spezifiziert wird. Wenn Sie Glück haben,
wird Ihnen das Handbuch zur Programmiersprache mithilfe einer formalen Sprache oder Grammatik-Bakus-Noro-Form
erweiterte Bakus-Noro-Form oder ähnlich sagen wie ein formal korrekter Satz der Sprache aussieht.
Viele User-Guides machen das einfach, indem Sie einem Beispiele geben.
Man muss erschließen, was die Regeln sind, was immer ziemlich hart ist.
Und dann Gibt es aber in der Welt der funktionalen Programmierung eigentlich die Vereinbarung,
dass man keine Sprache definiert, ohne dass diese Mantik formal spezifiziert ist.
Also in der Lisp-Welt ist es von Anfang an über das Lamder-Kalkül spezifiziert worden.
Und auch die Programmiersprachen, die danach kamen,
für die Lehrerausbildung Skim zum Beispiel und Recket,
die haben eine wohlfundierte Symantik, die man nachlesen kann,
wo eben gesagt wird, wie berechne ich die Bedeutung eines Ausdrucks?
Okay. Also wenn wir jetzt eine Programmiersprache haben,
können wir es mit dem ebengesagten so sagen. Wir haben drei Alphabete,
das Programm Alphabet, das sind die Buchsteine verwenden dürfen,
wenn wir das Programm schreiben, wenn Recket oder List,
Klammer auf, Klammer zu und Buchsterben und Ziffern dazwischen.
Dann haben wir ein Eingabealphabet, das, was Programme,
diese Programmiersprache lesen können und ein Resultatalphabet,
dass wir es Programme in dieser Programmiersprache als Ausgabe erzeugen können.
Und die Programmiersprache ist jetzt einfach ein Paar aus dieser,
an der Sprache L,
die formale Sprache über Sigma definiert und eben der berechenbaren Funktion,
die das Programmalphabet und die Die Eingabe abbildet auf den Oscar bei Alphabet entwerten,
die wir sehen können.
Und diese Funktionen erfeißt eben Semantik. Der Kluft,
mit der wir es häufig zu tun haben,
ist eben genau zwischen jetzt sehr dienotationellen Semantik.
Wenn wir formal definieren, was die Bedeutung einer Sprache ist,
ein Recket, in dem wir im Landerkalkülen hinschreiben,
was ein funktionaler Ausdruck jetzt ausgedruckt in der Church- und Lambland-Notation nur ist,
ne?
Und dann können Mathematiker hingehen und anwenden,
diese formale Notation und ausrechnen,
was rauskommt
Die operationale Semantik, das ist das, was unser Recket-System für uns macht.
Unser Programm, den Ostdruck, den wir geschrieben haben,
liest und versucht, den Wert zu finden.
Das Resultat dieses Ausdrucks ist. Und das ist die operationale Semantik.
Und dazu gilt er nach einer bestimmten festen Weise vor.
Und je nachdem, wie vorgegangen wird, kann es sein,
dass gar kein Wert gefunden werden kann,
wenn man es ungeschickt macht.
Oder, dass viele Werte gefunden werden. Und darüber wollen wir
heute reden. Wo sind die Unterschiede bei der Art,
wie diese Werte reduziert werden können?
Was kann denn passieren
Rackett in verschiedenen Ausprägungen. Und die unterscheiden sich
genau an der Stelle hier bei der operationalen Semantik.
Ich werde heute noch den zweiten Record-Di-Dialekt erwähnen. Lesi,
der reduziert direkte Ausdrücke in anderer Weise und kommt dabei zu anderen Werten.
Deshalb muss man die Semantik kennen, die notitionell,
was wir gemeint haben. Und zweitens wissen,
was zurzeit als Reduktionsmodell verwendet wird, damit wir auch sicher sind,
jetzt für das, was wir gemeint haben,
rauskriegen können.
Okay. Gucken wir mal
Einen kurzen Vergleich. Wie sieht das mit Semantik aus?
Sie haben Java verwendet. Ich kenne keine Programmiersprachen,
Handbuch für Java, das einem formell, die notationale Semantiken schreibt.
Es wird anhand von Beispielen erklärt. Also da haben wir keine Möglichkeit,
voll jetzt zu sagen, wir haben einen Java-Ausdruck geschrieben und wir beweisen,
was er bedeutet. Es gibt für Imperative Programmiersprachen,
das haben Sie hoffentlich sicherlich in den ersten zwei Semestern gelernt,
Korrektheitsbeweise zu machen, indem man hingeht,
Und Folgen von Anweisungen mit Zusicherungen versieht und dann über Schleifen in Varianten und Inferenzregeln beweist, was rauskommt.
Aber das ist sehr, sehr mühsam. Und die Zusicherungen,
die man braucht, sind in den Java-Handbüchern gar nicht da.
Die muss man sich selbst erschließen und sagen, ja,
ich glaube mal dran, wenn das und das gegeben ist,
dann kann ich anhand des Datentyps und so das haben.
In Regnet haben wir das Lambda-Kalkül. Wir haben ja gesehen,
wenn wir Lambda schreiben, Klammer auf und so weiter,
das kann man eins zu eins auf die Formeln des Landerkalküls
abbilden und dann können wir mit Hilfe von Logik und den mathematischen Regeln abgleiten,
was es bedeutet.
In Prolog haben wir die Logik und die Sprache der Relation
, um diese Mantiquests zu legen.
Auch das ist formal definiert. Und die operationale Semantik,
ja, die ist gegeben in Yava durch die Yava Virtual Machine.
Also solange sie dieselbe Virtual Machine verwenden,
auf allen Maschinen haben sie auch immer dasselbe Ergebnis.
Man müsste das Ding nur revert mit Reverse-Engineering zerlegen,
um rauszukriegen,
was es wirklich tut
Der Evalue Eda Eval, die Funktion Eval, ist das,
was für uns die operationale Semantik in der gesamten Listwelt macht, in Record in Skin.
Und in Frohlock ist die operationale Semantik, also für die,
die zurzeit bei Herrn Menzel die Vorlesung hören,
natürlich gegeben durch die Unifikation, die Ausdrücke, die wir eingeben,
die Klauseln, abgleicht mit der Datenbasis, die wir haben.
Und dann per Suche versucht, die Werte alle zu binden.
Also die operationale Semantik Ist einigermaßen klar. Und da werden wir
uns jetzt mal anschauen, was bei Racket da so wichtig ist.
Die wichtigste Vokabel, die sie sich bis zur Klausur
auf jeden Fall einprägen müssen, ist die Vokabel Betrübtenbezugstransparenz.
Das ist der Schlüssel für Semantik schlechthin. Und eine Programmiersprache,
bei der wir denotationale Semantik, also das,
was wir hinschreiben und das,
was der Rechner da halt daraus macht,
immer zum selben Ergebnis führen lässt,
die ist referenziell transparent oder man nennt sie auch deklarativ
Und das ist die Bezugstransparenz. Ja,
wenn wir etwas hinschreiben und es wird ausgewertet mit Hilfe der
operationalen Semantik und es kommt immer dasselbe heraus,
unabhängig von der Reihenfolge, in der es ausgewertet wird.
Dann haben wir eine Bezugstransparente Sprache, ja. Und die ist
nur dann gegeben, wenn wir die Programme so schreiben,
dass das Berechnungsresultat unabhängig ist, vom Zustand der abstrakten Maschine.
Und das zeigt das Bezugstransparenz, etwas ist,
was für uns in imperativen Programmiersprachen in die Haare reiben können,
Wo es im Wesen, wo wir im Folgen, im Wesentlichen,
im Imperativen programmieren, indem wir den Zustand der Maschine ändern.
Muss man sehr sorgfältig sein, um die Bezugstransparenz aufrechtzuerhalten.
Die hatten andere Namen,
Bezugstransparenz wird auch referenzielle Transparenz genannt oder sie wird Confluence genannt oder eben auch George Rosser-Eigenschaft,
weil die zum ersten Mal von Shorts und Rosser gefordert worden ist,
dass wir uns am Riemen reißen und so programmieren in einer
Programmiersprache,
dass das gegeben ist
Also die notitionelle Semantik, das, was wir hinschreiben und das,
was bei der Reduktion der Ausdrücke gemacht wird,
das ist nun mal dummerweise nicht notwendigerweise identisch.
Also zum einen kann es sein, dass die Implementation der
operationalen Semantik unter bestimmte Fälle nicht bedienen kann.
Wenn wir dahin schreiben, ist die Menge aller,
enthält die Menge aller Mengen, die sich nicht selbst entscheiden,
sich selbst. Dann können wir noch so gut,
rationale Semantik schreiben, dass diese Forderung ist nicht berechenbar.
Ja, das ist das eine. Also wenn die Sprache
uns erlaubt, nicht berechenbare Ausdrücke zu schreiben,
dann ist es unfair von der Programmiersprache zu fordern,
dass ihre implementierte Semantik das berechnen kann.
Denn das Zweite, wo wir stets und ständig die Bezugspransparenz verlieren,
ist, wenn wir mal schnell ein Schlupfloch nehmen in eine andere Programmiersprachenwelt.
In einem anderen Programmierstil. Also in Recic haben wir
die Bezugstransparenz sofort verloren
Wenn wir zwischendrin mal Set Bang oder irgendeine dieser Modifikatoren verwenden,
deren Name mit einem Ausrufezeichen endet. Das ist genau deshalb,
das ist der Grund, weshalb wir in dieser Vorlesung sagen,
Punktabzug für abgegebene Lösungen, wenn wir da ein einziges Ausrufezeichen entdecken.
Das sollte nicht, das verkostet die Bezugstransparenz.
Und sie sollen in diesem Semester lernen, so zu schreiben,
dass sie Bezugstransparenz aufrechterhalten. Also die ganzen imperativen Sprachelemente,
wo wir Werte und den Zustand der Berechnung verändern,
die können dazu führen, Das operationale und dienatutionale Sepmantik nicht mehr zusammenfühlen.
Und also salopp gesagt Seiteneffekte, ja. Bitte nicht verwenden in
diesem Semester. Und wenn wir in relationalen Programmiersprachen jetzt in der,
wenn Sie Ihre Übungsaufgaben für den Kollegen Benzel lösen,
hingehen und das Schöne ist verwenden für eine Wertzuweisung.
Und dann funktionale Ausdrücke verwenden, arrathmetische Auswerteumgebung und so weiter,
dann haben Sie da andere Probleme mit der und dinotationellen Semantik,
was sie dann verlieren, wer weiß es.
Was sie dann verlieren, ist die Richtungsunabhängigkeit. Wenn sie in der logischen Programmierung sind,
dann besteht ja die Auswertung des Programms darin,
dass in ihr Poloc-System versuchen wird,
die Variablen in der Anfrage mit festen Werten zu binden,
sodass der Ausdruck, den sie dann haben,
war es im Hinblick auf ihre Datenbasis.
Wenn sie fragen, Mörder XM, dann haben sie Eine Anfrage,
wo Sie fragen, welche Person hat wen ermordet?
Und je nachdem, ob Sie etwas vorgeben, zum Beispiel für M
mal Leonie und dann fragen Sie, wen hat Leonie ermordet?
Dann haben Sie auf diese Weise das Programm in eine Richtung verwendet. Wenn Sie jetzt fragen,
ich habe hier die Leiche Leonie und wollen jetzt wissen,
wer war der Mörder, dann binden Sie einen Wert fest
und halten für den Mörder die Variable frei und Ihr Prolog wird losgehen und versuchen, alle Mörder zu finden.
Das heißt, Sie können dieselben Regeln,
die sie definiert haben, das Programm vor- und zurückverwenden,
um in agile Richtung zu lösen.
Das ist eigentlich das, was am meisten Spaß bringt in der relationalen Programmierung.
Einmal schreiben und denselben Code in viele Richtungen, viele unterschiedliche Anfragen verwenden.
Ja, und diese Richtungsunabhängigkeit geht sofort flöten,
wenn sie in Pullock auf die Idee kommen, funktional zu programmieren.
Dann haben sie den Großteil der Mächtigkeit angebüßt.
Also diese hybriden Schlupflöcher im Funktionalen,
in die Imperative Welt und im Relationalen,
in die funktionale Welt,
die kosten unglaublich viel
Okay. Kommen wir jetzt mal. Zur operationalen Semantik und
zur Wertesemantik in Reckett. Was ist die Bedeutung eines Ausdrucks
? Also es ist eigentlich verblüffend einfach. Wenn wir
einen Ausdruck in einer funktionalen Programmiersprache schreiben,
dann ist seine Bedeutung der Wert, für den dieser Ausdruck steht.
Und wenn wir es noch so kompliziert machen,
3000 Geschachterte klemmern, über zehn Seiten Programmcode,
die Bedeutung des Ganzen ist der Wert, der rauskommt,
wenn man diesen Ausdruck auswertet.
Also einfachste Definition
Ein funktionaler Ausdruck oder Thermen wird einzig und alleine zu verwendet,
einen Wert zu bezeichnen, ja. Und das heißt,
die Bedeutung eines Ostdrucks ist der Wert, der der bezeichnet oder denotiert.
Und der Ausdruck hat keine andere Wirkung, als dass er jetzt
ein Berechnungsprozess auslöst, der diesen Ausdruck auf seinen Wert reduziert.
Und da kommt jetzt die operationale Semantik ins Spiel,
der Berechnungsausdruck ist ein Aufruf der Funktion F, der Semantik,
ja, in Practed Eval, die den Wert ausrechnet.
Und es darf keine versteckten Wirkungen oder Seiteneffekte geben
Das heißt, konkret, durch die Auswertung eines Terms
darf die Umgebung der Zuberechnungszustand der Maschine nicht verändert werden.
Und wenn wir das gegeben haben, dass die Berechnung eines Ausdrucks,
eines Schwerts, den Zustand der Maschine nicht verändert,
dann hängt der Wert eines Ausdrucks nur von den Werten der Teilausdrücke ab,
aus denen er zusammengesetzt ist.
Und da kommt das Substitutionsmodell ins Spiel. Also,
da der Wert eines Ausdrucks sich häufig aus zusammengesetzten Ausdrücken ergibt,
können wir die Teilausdrücke separat berechnen.
Und wenn wir einen Teilausdruck ermittelt haben und finden einen identischen Teilausdruck,
plus Quer von X-Quer von X, haben zweimal denselben Teilausdruck.
Dann reicht es, den einmal zu errechnen und dann einfach
zu substruieren für alle Teile, die genauso aussehen.
Also wenn wir den Ausdruck reduzieren, können wir Teiltherme durch ausgewertete Teiltherme ersetzen,
solange bis wir am Ende nichts mehr ersetzen können und dann haben wir Einen Ost haben wir den Wert.
Wir könnten ersetzen, wenn wir hier viermal 4 plus 3 mal 3 berechnen wollen,
ist es da eben dasselbe wie 4 mal 4 plus 9.
Da haben wir dreimal 3 reduziert und ausgerechnet mithilfe den
notationalen Semantik, was denn das Mal bedeuten soll.
Und dann können wir das Ganze nochmal ersetzen, indem uns auffällt,
aha, das erste hier ist die Wurzlaut 256,
wenn es denn Sinn macht, dass wir die Wurzel aus 81 und das ersetzen wir und kriegen 25 raus.
Also diese Eigenschaft, dass wir Teilausdrücke ersetzen durch andere,
ist die Bezugstransparenz, weil wir unabhängig von der Reihenfolge,
in der wir arbeiten, dieselbe Ersetzung vornehmen können, an verschiedenen Stellen.
Das geht eben nicht, wenn Seiteneffekte die Umgebung verändern.
Und das heißt, das, was wir hier vor uns haben,
ist ja so Pstitutionsmodell. Wenn der Wert eines Tumps ist jetzt,
bezogen auf Substitutionsmodell, der einfachste, die einfachste Repräsentation,
die wir für den Ausdruck finden können,
nachdem wir ihn vereinfacht haben, ersetzen, ersetzen, ersetzen,
Teilausdruck für Teilausdruck und am Ende haben wir einen schlichten,
einfachen Term der Einfachste, zu dem wir kommen können, das ist jetzt die kanonische Form.
Und das ist der einfachste Äquivalente Term. Ein Term ist
kanonisch oder in Normalform, wenn er nicht weiter reduziert werden kann.
Gut, schauen wir es uns mal an einem Beispiel an.
Das ist etwas, was auf ihrem Aufgabenblatt auch vorkommt.
Wir definieren hier eine Funktion, die quadrieren soll.
Die Feindsquare X ist mal XXX X. Wenn jetzt jemand den Wert rückt jetzt zum Fraß vorwirft,
ist quer plus 3 mal 7,
dann könnten wir in der Variante einen hingehen und ersetzen durchs Quer von Sieben,
indem wir nämlich plus 3, 4 ausrechnen, ja?
So, und dann schauen wir,
dann ersetzen wir es quer und stellen fest,
quer soll das Argument X mit sich selbst mal nehmen.
Und dann kommen wir zu mal 7, 7, ja?
Und wenn wir das ausrechnen, sind wir bei 49.
Das ist also die Variante 1. Hier haben wir zuerst
das Argument ausgewertet, dann die Funktionsdefinition ausgewertet.
Das ist übrigens genau das, was Srecked macht.
Zuerst gucken, bist du eine Funktion? Wenn ja,
dann fangen wir mal mit den Argumenten an.
Wertet ein Argument aus und am Ende haben wir einen Wert
Nennt sich innere Reduktion, weil wir zuerst hier mit den Argumenten
begonnen haben. Die Variante 2, das ist das,
was in Häskel und Miranda gemacht wird.
Da wird zuerst mal geguckt, okay,
hier beginnt der Ausdruck, was habe ich an Square?
Und dann wird geguckt, was ist denn Square?
Square bedeutet das sich einmal nehmen muss und dann das Argument
X zweimal hinschreiben soll und das viel genauer gemacht worden,
das Argument X zweimal hingeschrieben und Klammer
So, und jetzt gibt es hier nichts mehr auszuwerten,
mal sagt,
ich muss multiplizieren und dann wird mir ran der Zähne knirschend feststellen,
jetzt muss ich tatsächlich mal eines der Argumente auswerten,
sonst kann ich mit dem Malen nicht weitermachen.
Da wird es zuerst das erste auswerten.
Dann stellt es fest, oh, geht immer noch nicht.
Das zweite ist ja noch nicht ausgewertet. Jetzt haben wir
hier sieben mal sieben stehen und das Ergebnis ist 49.
Also Variante 2 besteht darin,
zuerst die Funktionsdefinition zu expandieren und die Argumente mit den Argumenten
zu versehen und dann loszurechnen und immer erst ein Argument auszuwerten,
wenn es absolut nicht anders geht
Das heißt, äußere Reduktionen, weil man von außen nach innen geht.
Es hat auch den zweiten Namen Lezy, die faule Auswertung,
weil man das Auswerten der Argumente so lange vor sich her schiebt.
Die gibt die Git, bis man nicht anders kann.
So, die Reihenfolgeunabhängigkeit des Wertes ist jetzt ein wichtiger Satz.
Also solange wir Bezugstransparenz haben,
wenn die Bezugstransparenz unser größtes Silictum in dieser Vorlesung hier gewährleistet ist,
dann ist es egal, wie ausgewertet wird.
Ob wir zuerst mit den Argumenten anfangen oder der Funktionsdefinition.
Das einzige was sich ändern kann, ist unser berechender Aufwand.
Aber die Reihenfolge bestimmt nicht den Wert. Wir werden denselben Wert herausbekommen.
Solange wir Bezugstransparenz haben und wenn der Ausdruck berechenbar ist,
kriegen wir denselben Wert heraus, unabhängig davon,
ob wir von außen oder von innen beginnen.
Aber die Reihenfolgen, Reduktionsreihenfolge kann dazu führen, dass wir
ziemlich lange rechnen müssen, wenn wir es ungeschickt machen.
Oder dass wir eventuell gar keinen Wert bekommen. Also in
diesem Fall kann man es schon sehen, Quadrat von 3 mal 4.
Das Expandieren von Square, ja, das Tier,
was ja definiert, ist quer von X ist X und bei X.
Da muss also das Mal genommen werden.
Und dann die beiden Werte, der Wert für X,
der übergeben wurde, zweimal hingeschrieben werden. Das heißt,
wir müssen das 3 plus 4 mit einmal zweimal berechnen.
Und haben, also da doppelten Aufwand gehabt,
dass wir bei der vorgezogenen Auswertung nicht haben.
In der Welt von Viranda, Heskel und den strengen getypten Programmiersprachen
ist es sehr typisch, dass man die äußere Reduktion hat.
Das heißt, das Ausrechnen der Argumente,
solange vor sich her schiebt, wie irgend möglich.
Es kann einem passieren, dass die überhaupt nicht berechnet werden.
Also diese äußere Reduktion, die ist ziemlich ungünstig,
wenn das Argument mehrfach gebraucht wird.
Also zum Beispiel Hoch 4 von X,
da haben wir viermal X stehen und wenn wir hier unten
ausgewertet viermal das X reinschreiben, dann wird dasselbe viermal berechnet.
Kann unangenehm sein. Die innere Reduktion wird zuerst
die Argumente ausziehen, auswerten.
Die kann
ziemlich ungünstig sein
Wenn die Argumente überhaupt nicht gebraucht werden. Was habe ich
hier definiert? Eine konstante Funktion. Die nimmt drei Argumente
einfach nur so zum Spaß. Und wenn ich hier jetzt
Lives the Universe and Everything auf jeder Parameterposition berechnen will,
kann das unglaublich lange dauern. Beim Aufruf von Constant,
erst X zu berechnen, dann Y,
dein Set zu berechnen, um am Ende festzustellen,
hätte ich mir alles sparen können.
Ich habe von vornherein sehen können, das Resultat des Eins.
Hier gewinnt die äußere Reduktion die Argumente nie auswertet,
außer es im Rumpf der Funktion stößt man darauf,
dass sie gebraucht werden
Ja. Okay. Das erste, die vorgezogene Ostfährt,
und die heißt deshalb auch Igar Evaluation, also eifrig, Funktion steht da.
Ich sehe, oh, da sind die Argumente.
Nichts wie loslegen, rechnen wir erstmal alle Argumente aus.
Und hinterher denkt man darüber nach,
ob man sie braucht und wofür sie gebraucht werden.
Aber erstmal, da ist der Auftrag, ich mache es.
Bevor ich mir anschaue, was damit geschehen soll,
ich werde sie alle erstmal aus.
Unter der Annahme, der Programmierer, die Programmiererin,
werden sie schon was dabei gedacht haben,
wenn sie diese Argumente vorsehen.
Also muss ich
sie auch auswerten
Und wenn ich sie alle schon ausbewertet habe, dann wenn
sie drei oder viermal gebraucht werden, habe ich gespart.
Ich finde nur einmal ausgerechnet worden. Die Lazy Evaluation,
so die Beamtenmentalität. Oder auch heißt auch Normalform der Auswertung.
Dann guckt sich der Evaluationsalgorithmus erstmal die Funktionsdefinition an. Die Argumente,
die werden ad acta gelegt, die kommen ins Archiv,
wenn sie jemand anfordert, guckt sich die jemand an.
Aber erstmal wird sie angeguckt, was will die Funktion eigentlich tun?
Die Funktion wird expandiert. Und wenn nicht,
dann absolut nicht mehr weitermachen kann, ohne dass ich angster Argument auswerte,
werde ich es immer auswerten, wenn es gebraucht wird.
Und das kann dazu führen, dass man sich viel Arbeit gespart hat,
weil die Hälfte der Argumente eventuell gar nicht gebraucht wurde.
Also das ist die faule Art, erst bei der Anmahnung,
wenn man bei der Auswertung im Rumpf der Funktionen drauf stößt,
wie brauche ich das Argument, dann wird es dann auch gemacht.
In Recket ist die operationale Semantik so substitutionsmodell. Also die Zahlen,
der Wert einer Zahl, dass die Zahl setzt,
wenn da irgendwo eine Zahl auftaucht,
werde ich sie aus und gebe die Zahl zurück.
Der Wert, Bezeichners, also unsere Symbole,
das sind die Werte, die in die Symboltabelle eingetragen sind.
Da holen wir sie her, wenn sie gebraucht werden.
Zusammengesetzte Ausdrücke werden durch Substitusitionen zu atomanen ausdrücken, reduziert.
Und dann haben wir noch Special Forms Expressions.
Und das sind die, wo die Auswertung abweicht und
die müssen wir uns als nächstes mal anschauen.
Gut, also für die funktionalen Ausdrücke sprechen wir von applikativer Auswertung.
Erst werden die Argumente ausgewertet und dann wird die Funktion
darauf appliziert
Die Reihenfolge ist allerdings undefiniert. Also in Bracket und Skim
ist nicht garantiert, dass die Argumente von links nach rechts
ausgewertet werden, sondern es ist bewusst freigehalten worden.
Es ist undefiniert. Wir wissen nur,
bevor die Funktion darauf angewendet wird, sind alle Argumente ausgewertet.
Also, was Racket macht, ist der erste Teilausdruck in
der eröffnenden Klammer, wird zuletzt ausgewertet.
Racket schürt nur mal so ein bisschen dahin, um zu sehen,
ist es eine Funktion oder ist es eine Special Form?
Wenn es eine Funktion ist, wird das zurückgestellt,
die Argumente ausgewertet. Wenn es eine Special Form ist
wie If oder Denn oder Els oder was auch immer,
dann wird von der festgelegten Reihenfolge abgewichen.
So, und das führt rekordsiv auf Teilausdrücke angewendet.
Also wir haben eine strikte innere Reduktion. Das heißt,
wenn wir die Argumente hinschreiben, müssen wir davon ausgehen,
dass die wirklich alle ausgewertet werden, auch wenn sie nicht gebraucht werden.
Wir haben noch Modifikatoren. Ich will sie mal,
habt ihr ja schon erwähnt, um zu sagen, was sie nicht verwenden sollen.
Also wir können, wenn wir eine Variable definiert haben,
Wurzel auf dem Wert 1. Können wir den Wert verändern mit Set-Bang
? Quadratwurzel aus 4. Und was man aber jetzt sieht,
ist, wenn ich hier Wurzeln, mal Wurzeln rechne,
in dem Fall, Quadratwurzel aus 4, dann kriege ich 4 raus.
Wenn ich aber Set-Bang mache und Wurzel jetzt zur Quadratwurzel von 8
ändere und denselben Teilausdruck nochmal hinschreibe, dann kommt was anderes raus.
Also diese Modifikaturen verletzen leider die Bezugstransparenz.
Derselbe Ausdruck mehrfach geschrieben kann unterschiedliche Werte haben.
Typisch für die Imperative Programmierwelt in der funktionalen Programmierwelt unerwünscht
Ausdrücke, die mit jetzt speziellen Operatoren beginnen,
Special Form Expressions, die heißen eben Special Forms, ja.
Wenn Special Form Operator haben, mit dem der Ausdruck angeleitet wird,
dann ist das immer ein Signal, ein Recket,
hilft nichts, wir müssen jetzt von der typischen,
strikten Auswertereienfolge für Funktionen abweichen.
Also die fein Quote IF zum Beispiel. Und gucken wir uns
mal an, warum. Wenn wir etwas Wenn wir diese Quote schreiben,
dann wollen wir doch verhindern, dass es ausgewertet wird.
Es sollen unquote, es soll unausgewertet an den Wert gebunden werden.
Wenn Quote eine Funktion wäre, dann würde zuerst hier die
Funktion eins auf die Argumente 2 und 3 angewendet werden.
Wenn man jetzt auch die Liste 1, 2, 3 auswertet.
Also das will man nicht, sondern man möchte die quotierte
feste Liste 1, 2, 3 haben.
Und deshalb ist Quote eben eine Special Form, die das Auswerten der Argumente unterdrückt.
Es ist keine Funktion. Die Feinden ist auch keine Funktion,
weil Wenn die fein eine Funktion wäre, dann so,
ja, hier steht jetzt die feinen Zahlen,
portiert diese Liste, dann würde, wenn es eine Funktion wäre,
würde man zuerst zahlen, versuchen auszuwerten.
Aber das ist ja noch undefiniert. Also würde es sofort krachen.
Also auch ein Define wird zuerst das zweite Argument auswerten,
garantiert und es dann an das Symbol hier binden.
Okay, das hatten wir eben schon besprochen.
Und wir brauchen es ganz, ganz dringend für Fallunterscheidungen.
Wenn wir sowas wie ein IF haben wollen,
wir haben eine Bedingung, Und wenn diese Bedingungen erfüllt ist,
dann wollen wir nur diesen zweiten Fall beenden, auswerten.
Wenn die Bedingung nicht erfüllt ist, wollen wir nur diesen Teil aushändeln.
Wenn es eine Funktion wäre, dann würde Folgendes geschehen,
Bedingungen auswerten, einen Senteil ausführen, Elsteil ausführen.
Und dann wird geguckt, was ist das IF eigentlich?
Das wäre extrem unpraktisch. Das heißt,
man muss eben dafür sorgen, weil ein IF bei Fallunterscheidung,
dass von der undefinierten Reihenfolge, in der die Argumente ausgewertet werden,
abgewichen wird und für eine garantierte feste Reihenfolge haben,
hat die Bedingung getestet und abhängig von der Bedingung,
das eine oder das andere.
Und das Gleiche gilt fürs Kont oder Käse oder ähnlich, ne?
Okay. Also wenn das If eine Funktion wäre,
dann hätten wir ein Problem, wenn alle Teile ausgewertet werden,
dann würde zum Beispiel ja auch die illegale Division eins durch X gemacht werden.
Im Fall wirkt es gleich null ist, wo wir es
nicht wollen, ja. Gut, die Feinset,
Let, Let, Stern, Käse, Kont,
das If, das Quot und das Deleg,
was eine Berechnung zurückstellen soll für später,
die müssen natürlich alle
speziell behandelt werden
Oder Beginn, das ist ein Teil um den Recket Imperativ zu werden.
Da können wir eine Folge von Ausdrücken sequenziell abarbeiten.
Am Beginn macht dann Sinn, wenn wir,
sagen wir mal, eine Eingabe vom Benutzer lesen wollen.
Denn müssen wir ja als erstes sagen, gib mir bitte mal
einen Wert für X. Und dann rufen wir Ritt auf,
um das X zu lesen. Wenn wir keinen Beginn hätten,
dann könnte es sein, dass der Rechner zuerst versucht,
X zu lesen und wartet und wartet und wartet,
weil er noch nicht die Anfrage ausgegeben hat,
gib mir mal ein X.
Also es gibt auch funktionalen Programmierstilsituationen,
wo wir bestimmte Sachen in einer bestimmten Reihenfolge haben wollen.
Beim Lesen von der Platte wollen wir die Variablen in der
vorgesehenen Reihenfolge binden und ähnliches.
Und für solche Fälle haben wir eben das Beginn.
Da können wir eine Sequenz von Anweisungen hinschreiben hier.
Ich finde eine schöne Ausgabe und so weiter.
Gut. Gibt zu den Special Forms erstmal Fragen.
Nö, wenn nicht. Gut, dann gucken wir jetzt nochmal,
warum zu einer wohldefinierten Programmiersprache auch ein undefinierter Wert gehören muss.
Das ist in allen funktionalen Programmiersprachen vorgesehen
Das Werte, wo die operationale Semantik alle Viere von sich streckt und keinen Wert finden kann,
dass die aber trotzdem einen Wert zurückgeben müssen,
weil jeder Ausdruck einen Wert haben muss.
Ja, und wenn man keinen finden kann,
dann nimmt man eben den undefinierten Wert. Ein Beispiel,
wir werden den undefinierten Wert mal erzwingen können,
ist und konditional, was nur den Fall hat,
der immer falsch ist, der niemals wahr ist.
Im Fall wohl Wo es, das konditionell nicht erfüllt,
es geht mir den Wertanzug, der kann natürlich nichts zurückgeben,
also kriegen wir den undefinierten Wert zurück.
In Recked ist das meistens einfach eine unsichtbare Lehreausgabe. Ja,
die externe Repräsentation des undefinierten Wertes kann variieren. Wir schreiben es,
wenn wir Beweise machen und den undefinierten Wert ansprechen wollen durch dieses Bottom-Symbol,
aber das ist nicht das,
was Recked uns zurückgibt,
sondern der undefinierte Wert kann uns gezeigt werden durch eine Fehlermeldung
oder eben durch eine Lehreausgabe
Gut. Also das hängt auch wirklich von der Sprache ab,
mit der wir zu tun haben, die verschiedenen Skim-Dialekte.
Da kamen an Specify zurück, in X geben und OWMs geben,
da kriegten wir den Namen der definierten Variablen mit einem Define,
die kamen dann mit der leeren Liste zurück.
Also es war wirklich, es ist wirklich seltsam,
was passieren kann. Das einzige,
was wir über einen undefinierten Wert hier festhalten sollen,
ist, weil er eben undefiniert ist,
können wir ihn
nicht hinschreiben
Wir sollten uns nie auf eine spezielle Repräsentation verlassen.
Also, die externe Repräsentation ist beim undefinierten Wert auch undefiniert.
Und wir können sie nicht abfragen im Programm, ja?
Weil wir dann implementationsabhängigkeiten haben. Wir haben in Recke zwei
verschiedene Arten für undefiniertheit. Also, wenn wir ganz bösartig wollen,
dass eine Funktion nur undefinierte Werte zurückgibt,
dann können wir schreiben,
Dass die verabsichtigte und definiertheit. Die wird für meinen Geschmack
viel zu oft von den Recket implementieren verwendet.
Also überall, wo wir imperative Sprachelemente verwenden oder Dinge definieren
, kommt als Wert Volt zurück.
Das ist eine beabsichtigte Undefiniertheit und die können wir abfragen,
indem wir die Funktion Volt Fragezeichen nehmen und testen,
ob der Ausdruck einen Wert hätte oder nicht.
Und dann gibt es die fehlerhafte und definiertheit,
der kriegen wir an die Feind zurück
Also Funktionen, die nur durch Seiteneffekte wirken, die haben
eine beabsichtigte Undefiniertheit. Und die geben den leeren Wert wohl
zurück. Und wenn es crasht oder wir Fehler kriegen,
dann kriegen wir einen Feind zurück.
Der Wert von uninitialisierten Variablen zum Beispiel. Wir können es
abfragen mit dem Prädikat Void. Und wir können den leeren
Wert erzeugen. Mit dem Funktionenwort rufen Void auf mit Argumenten,
beliebig vielen und kriegen dann den undefinierten Wert zurück.
Wollte 1, 2, 3,
gibt uns für das Undefinierte
nichts zurück
Und wenn wir fragen, ob das was wollt,
1, 2, 3 zurückgibt, Lea ist,
dann kriegen wir Truth zurück, ja?
Wir können auch ein Ex definieren mit dem leeren Wert
und kriegen dann für X, die beabsichtigte Undefiniertheit zurück.
Okay. Sie werden es sehen, dass an manchen Stellen ganz sinnvoll ist,
wenn wir Funktionen haben mit mehr Argumenten als gebraucht werden,
dann können wir manchmal eben ganz gut den leeren Wert dort übergeben.
Denn wirklich den unbeabsichtigten Wert unbeabsichtigt undefinierten Wert zu kriegen,
das ist ganz schön schwierig,
weil einem das meistens trifft,
wenn etwas nicht berechnet werden kann und dann kriegen wir in
der Regel eher eine Fehlermeldung
Also diese Funktion Strange hier,
die würde uns ein Angefeind geben,
wir definieren eine Variable, die noch nicht definiert ist,
mit sich selbst mit der undefinierten Variable und geben dann den Wert zurück.
Dann kriegen wir an die Feind zurück. So,
noch eine wichtige Vokabel, das ist die Stricktheit einer Funktion.
Die Funktion, die heißen Strickt. Wenn sie nur für
definierte Werte einen definierten Wert zurückgibt. Das heißt,
wenn irgendeins der Argumente undefiniert ist,
kriegen wir auch den undefinierten Wert zurück
Und eine Programmiersprache hat eine strikte Semantik, wenn alle Funktionen,
die man überhaupt definieren kann, strikt sind.
Und Racket ist so eine Sprache, ja. Racket hat eine strikte Semantik,
was man schon daran sieht, das rackelt sich eben mit Eager Evaluation
vorgezogene Auswertung erstmal auf alle Argumente stürzt und sie auswertet.
Und wenn da ein Spy ist, das nicht evaluiert werden kann,
weil es undefiniert ist, dann kriegen wir einen Fehler.
Das heißt, die Funktion würde in dem Fall,
wo eins der Argumente undefiniert ist,
einen Fehler geben
Gut, also das hatte ich schon erklärt gehabt. Und weiter,
damit wir aber mit undefinierten Werten umgehen können,
dafür brauchen wir jetzt eben die Special Form Expressions,
wo wir eine festgelegte Reihenfolge haben und Dinge,
die eventuell undefiniert sind, vermieden werden können.
Das sind eben diese F-Cont-Case, also in Special Forms
dürfen wir undefinierte Argumente haben, in Funktionen nicht.
Die nicht strikte Sickgappen, Semantik kann aber ganz attraktiv sein
und deshalb ist sie eben auch in Miranda, Heske,
in dieser Welt der streng getypten Sprachen
weit verbreitet
Ja. Ich kann,
wenn ich etwas definieren kann mit Nichtstrick der Semantik,
dann kann ich in der Substitution die Arc-Teil-Ostdrücke,
die undefiniert sind, einfach weiterverwenden und weiter ersetzen.
Und ich kriege jedes Mal das richtige Ergebnis. Nehmen wir mal an,
wir haben hier definiert die Funktion Fried von Ex, hat den Wert
3. Wenn ich für Recket hier eins durch Null übergeben würde für Sree,
würde es knallen. In Miranda würde es gut gehen,
weil Miranda gar nicht als das Argument auswertet,
sondern gleich die drei zurückgibt
Egal, ob das Argument ausgewertet werden konnte oder nicht.
Und dann sind Beweise einfacher, weil wenn ich überall diese
Sonderfälle nachdenken müssen. Wir können überall, wo es lief,
von Ex auftaucht, einfach den definierten Wert 3 reinsetzen,
unabhängig, ob das Argument hätte ausgewertet werden können oder nicht.
In Rackett dagegen würde, wenn wir einen undefinierten Wert übergeben,
der Wert undefiniert sein und dann kann ich nicht diese einfache Substitution machen.
Okay. Die strikte Auswertung, die ist eben Gerade weil so ein If,
wie wir es auch auf dem Aufgabenblatt haben, ziemlich problematisch,
wenn ich mein eigenes My-If definiere, wenn dann sonst?
Ja, und dann es konditional habe, wenn, wenn,
erfüllt ist, dann soll dann gemacht werden, andernfalls sonst.
Ich komme nicht zu dem Test, wo dies konditional durchgeführt werden soll,
wenn eins von den dreien mir oben nicht ausgewertet werden kann.
Ja, das ist genau das Problem. Okay. Ja,
wenn wir uns mal anschauen, in der Liste und Tracket-Welt,
da haben wir eben generell diese strikten Funktionen.
Wir haben gesehen, das ist Sehr, sehr vorteilhaft,
wenn ich die Argumente mehrfach brauche, sie werden zu Anfang einmal ausgewertet.
Jedes wird nur einmal ausgewertet. Und ich kann damit effizienter sein.
In der... Und wir können uns darauf verlassen,
dass alles ausgewertet worden ist. In der Miranda- und Teskelwelt
dagegen wird nur ausgewertet, was wirklich gebraucht wird.
Und in dieser Welt ist es dann recht schwierig, wenn ich,
sagen wir mal, erstmal eine Aufforderung für eine Druckereinausgabe eingeben will.
Und dann
Will ich irgendwas machen? Ich muss es erzwingen, dass
diese Druckerausgabe erfolgt. Oder wenn ich keine Kontrolle darüber habe,
in welcher Reihenfolge von einer Platte gelesen wird,
dann kann es ziemlich zufällig sein, welcher Wert an welcher Variable gebunden wird.
Also in der Sprache haben wir Argumente, Befores und Zweck,
die erzeugen, dass etwas gerechnet wird, auch wenn wir es,
wenn Miranda denkt, wir brauchen es nicht, ne?
Okay. Gucken wir mal auf einen ganz anderen Recket-Dialekt,
Wenn wir Lesy-Programmierung in Recket genießen wollen, dann können wir
einfach mal unser Recket-System hier zum Spaß setzen auf langen Lesy.
Und da können wir definieren, immer drei, alwaysly für einen Wert von X.
Und wenn wir in der Welt von Lesy sind,
dann wird es gut gehen.
Da können wir auch als Argument eins durch Null übergeben.
Es wird gar nicht berechnet, sondern wir kriegen gleich drei zurück.
Also diese Lesy-Avaluation, die können wir eben in Modulen machen,
nur weil als Programmiersprache Lesy
geschrieben haben
Lese, können wir auch unser My-IF selbst definieren?
Wenn wir da schreiben, My-IF, wenn dann sonst.
Und nur wenn der Arztfall erfüllt ist, soll sonst zurückkommen,
dann können wir hier das My-IF so machen.
If not zero x. Dann würde in dem Fall,
wo x gleich null ist,
die Division durch null vermieten werden und wir kriegen Volt zurück.
Und das Angenehmste, was wir in der Welt der faulen Auswertung haben.
Lese Evalue Isn. In den meisten Fällen brauchen wir nicht mal
bei der Rekursion Abfruchtbedingungen vorzusehen. Weil in der Lese-Welt muss man
die Auswertung wirklich anstoßen, damit die Rekursion ins Unendliche geht.
Solange wir nicht nach Werten fragen, werden sie nicht berechnet.
Und wenn man sich angewöhnt hat bei Rekursionen immer zuerst zu fragen,
habe ich die Abbruchbedingungen und dann tue ich was,
dann kriegt man eine Gänsehaut, wenn man sieht,
wie man das in Lesing machen kann.
Ich kann leicht und locker definieren,
die Menge aller natürlichen Zahlen, Ab der Zahl N,
ja, indem ich einfach sage,
die Mängel der natürlichen Zahlen ab der Zahl N besteht darin,
dass sich die Zahl N nehme und dann die natürlichen Zahlen ab N plus 1 berechne.
Ja, das kann dauern. Tut es aber nicht,
ja. Solange keiner nach der tausendsten natürlichen Zahl fragt,
werden die nicht ausgerechnet. Was Lese geschieht, ist,
dass dieses Konzil gemacht wird und dann wird gesagt,
okay, diesen Teil hier,
den stellen wir mal zurück,
bis jemand tatsächlich
eine der weiteren Zahlen haben will
Wir brauchen keine Abbruchbedingungen für Rekursion, weil die Werte immer angefordert werden müssen.
Sie werden nur berechnet, wenn sie gebraucht werden.
Also, mit dieser Definition eine Rekursion über natürliche Zahlen ohne
Abbruchbedingung beginnend von null bis unendlich.
Kann die folgende schreiben? Natürliche Zahlen ab eins,
und ich verwende darauf, die Funktion Listreff an 1000,
dann kriege ich heraus, aha, das ist die Zahl
10001. Super, ne? Und wenn ich mir anschaue,
was natürliche Zahlen ab eins gibt,
dann sieht man
dieses Lesi
Wir kriegen eine Liste zurück, die beginnt schon mal mit
der Eins. Dieses Konzier ist gemacht worden. Und für
den Rest steht der Promis. Das ist der Aktenvermerk,
wenn weitere Zahlen gebraucht werden, wird dieses Promis ausgewertet,
das Versprechen ausgeführt. Und man guckt nach weiteren Zahlen.
Aber nicht so lange sich keiner dafür interessiert. Irgendjemand muss
den Wert anfordern, dann wird der Rest hier ausgeführt.
Aber wir können in der Lesi-Welt wirklich Folgendes machen,
indem wir sagen, Funktionen schreiben über der Definitionsmenge aller natürlichen Zahlen.
Wir können Mapping-Funktionen über die Liste der natürlichen Zahlen machen und einfach sagen,
bilde die Menge aller natürlichen Zahlen ab, ihr Duplikat und so weiter.
Und das steht alles da, ohne dass man
von hinten beginnen muss und gegen die Verankerung eins geht,
sondern wir können wirklich aufsteigendes machen.
Die Rekursion über den Nachfolger in der Racket-Welt,
wenn wir bei langen Rackets sind,
dann machen wir die Rekursion immer über den Vorgänger,
damit wir irgendwann die Abbruchbedingungen bei der Null- oder Angst erreichen.
In Lesi machen wir die Rekursion über den Nachfolger einer Zahl
und für den Nachfolger wird eben immer dieses Pommes hier abgelegt,
solange bis hier jemand wirklich
diesen Wert braucht
Wenn wir aber erzwingen, dass wir jeden Wert sehen wollen, ja.
Wir machen die natürlichen Zahlen ab eins und haben mir aber noch
die Funktion Echo, die den Kötter von X zeigen soll.
Oh Gott, dann haben wir erzwungen,
dass für jedes Promis geguckt wird, was der Wert ist.
Und in dem Fall müssen wir dann den Finger auf
der Abbruchtaste haben,
um diese Rekursion zu beenden
Ja, zur Semantik gehört natürlich der Begriff Korrektheit eines Programms.
Nicht, wenn wir eine wohldefinierte Semantik haben,
dann können wir versuchen zu beweisen, ob ein Programm richtig ist.
Und wann ist ein Programm richtig? Ich beziehe mich hier
mal auf die Definition aus Hans Züllikovenskript von 95.
Da hat er geschrieben, ein Programm heißt korrekt.
Wenn es genau die Spezifikation erfüllt, also für alle Argumente
des Definitionsbereichs, die korrekten Resultate bezeichnet.
Und das heißt partiell korrekt, Wenn es sofern terminiert,
korrekte Resultate liefert. Das ist eine sehr bequeme,
sehr bequemer Ausweg, wenn wir unser Programm so schreiben,
dass es nie terminiert, dann ist es korrekt.
Weil das, wenn der Fall, dass es terminiert,
nicht vorkommt, haben wir ein korrektes Programm.
Ist meistens nicht das, was die Kunden haben wollen.
Aber es ist mathematisch gesehen korrekt. Und ein Programm heißt
total korrekt, wenn es für alle Eingaben mit korrekten
Resultaten terminiert
Gut, wie stellen wir die Korrektheit sicher?
Wir haben den formalen Korrektheitsbeweis, ja,
indem wir die Zusicherungen nehmen und daraus ableiten, was passiert.
Wir können es vollständig testen, wenn der Definitionsbereich klein ist,
sagen wir, es gibt nur drei erwartete Werte,
dann rufe ich es dreimal auf.
Oder wir verwenden eine Konstruktion für das Programm,
bei der wir es aus korrekten Teilen zusammenbauen.
Programmsynthese oder Programmtransformation. Wir schreiben eine korrekte Spezifikation und wenden
einen Generator, der es übersetzt,
in ein korrektes Programm
Als ich funktionales Programmieren gelernt habe, der saß ich bei John McCarthy
in der Vorlesung und der hat als erstes Gesang, die Bugging is it discrace.
Die Herausforderung an Programmierers, wenn man einen funktionalen Programmiersprachen arbeitet,
ist, dass man wirklich ein Programm systematisch entwirft und das läuft auf Anhieb.
Das ist der Traum des funktionalen Programmierens, so wie es von McCarthy gelernt,
weil der war auch ziemlich gnadenlos, also wenn er Studenten gesehen hat,
übernächtigt, Irgend müde rum und haben dann gesagt,
oh Mensch, ich habe jetzt drei Nächte debakt,
aber endlich habe ich den Fehler gefunden und die waren so stolz,
dass sie einen Fehler gefunden haben, hat er gesagt,
Schande über euch,
eine Fehler hätte der nicht machen dürfen.
Und es ist, wenn man sich die Lehrbücher für
funktionales Programmieren anguckt, dann sieht man auch tatsächlich,
die sind alle so aufgebaut von dieser heilen Weltillusion.
Wir schreiben von mal die Spezifikation hin,
was das Ganze tun soll, implementieren es korrekt und dann
funktioniert es auch.
Die Bedingungen, dass man diesen Weg gehen kann und zumindest es versuchen kann,
meistens gelückt, glückt es ja auch, ist,
dass für jede nicht triviale Funktion mit Zusicherung versehen, ja, Ressourcens.
Wir schreiben in der Klausel der Funktion,
worauf man sich verlassen können soll,
wenn die Funktion ausgeführt wird und schreibt auch Zusicherungen hin,
die dann erfüllt sind fürs Resultat.
Und wenn wir neue Funktionen schreiben,
versuchen wir auf den Zusicherungen für die Resultate der Teilfunktionen aufzubauen.
Wenn einem das gelingt, mit Vorbedingungen,
Nachbedingungen, dann haben wir schon gewonnen,
Und Bezugstransparenz, die ermöglicht es uns, dass wir tatsächlich so arbeiten.
Okay. Das Überprüfen der Argumente, ja, wir haben schon gesehen,
in Reckett müssen wir nicht mal die Argumente definieren.
Sie sollten sie aber in allen Funktionen,
die in den Schnittstellen von ihrer Modulen auftauchen,
für die aufrufenden Module natürlich mit Zusicherungen absichern.
Und in Dr. Reckett haben wir eine feine Sache,
das sind die Contracts. Ja, wir haben das Vertragsmodell
als Basis und können für jede Funktion für jede Variable,
für jede globale Variable einen Vertrag definieren,
der überprüft wird,
wenn wir
das Ganze testen
So ein Kontrakt erzeugen wir mit speziellen Konstrukturen. Und diese Verträge werden
dann überprüft, wenn unser Modul geladen wird. Zum Beispiel,
wenn wir eine globale Variante 3 im Modul haben, ja,
und wir wollen hier dieses x-Stern. Und wir wollen sicher sein,
dass der Wert für diese globale Variable garantiert immer zwischen 0
und 3 ist und wir verlassen uns im Rest des Kurs drauf,
dann können wir einfach schreiben, die fine Contract,
wir haben den Vertrag, die globale Variable X muss immer erfüllen,
der Wert hier, between contract liegt zwischen 0 und 3.
Und wenn ich diesen Vertrag jetzt und ich die Variable hier initialisiere,
also es ist eine Definition mit einem Vertrag und hier kommt der Wert,
den sie kriegen soll. Da sieht man schon,
ich habe hier eine etwas unzuverlässige Eingabe vorgesehen, die kann eine Benutzereingabe sein,
wenn ein Benutzer nicht lesen kann, wenn man dem sagt,
bitte gib eine Zahl zwischen 0 und 3 an und da gibt es 5 ein.
Ja, Pech gehabt. Dann passiert beim Laden dieses Moduls das Folgende,
dass ich die Meldung kriege, definition ex broke the contract between,
erwartet wurde etwas, was zwischen 0 und 3 ist,
aber lustige User hat einen gegeben, 4.0-2. Das heißt,
alles, was worauf wir Bezug nehmen und was wichtig ist,
können wir mit solchen Verträgen absichern.
Und die Verträge, die wir schließen können,
das ist einmal dieser Define-Contract, wo wir zum Beispiel sagen können,
Flat-Contract, dem können wir hier eine Funktion übergeben.
Irgendeine Funktion, die wir in Recket definieren können,
die wird angewendet, um zu prüfen,
ob diese Variable korrekt definiert ist.
Wir können einen Vertrag,
den wir mehrfach verwenden wollen,
den können wir benennen
Ja? Vielleicht nehmt Kontrakt ungerade. Dann wüssten wir,
ah, der Vertrag ungerade ist gebrochen worden.
Oder wir können das sehr diffizil machen, zum Beispiel,
dass wir sagen, der Wert muss eine Liste sein.
Ja, List of. Flat named Contract und gerade Ott,
das haben wir gesagt, diese Liste darf,
der Wert der Variablen darf nur eine Liste mit ungeraden Zahlen sein.
Und das hilft uns also beim Testen,
in dem all das, was wir als Voraussetzung haben,
in Verträgen
explizit gemacht wird
Es sind einige weitere Beispiele. Also Verträge sind mächtiger als Typdeklarationen,
weil hier da alles, was wir in Recket ausdrücken können,
als ein Prädikat genommen werden kann.
Und weil wir Abhängigkeiten zwischen Werten in den Verträgen beschreiben können.
Okay, gut. Ich habe hier als letztes Beispiel,
das können Sie sich dann auch übers Wochenende mal zu Hause anschauen.
Unser Beispiel Seitenlänge eines Dreiecks,
wo wir ja die Zusicherung hatten, dass gefordert war,
das Argument A,
B und C
sollten aufsteigend sortiert sein
Und wir könnten diese Funktion absichern mit einem Define-Contract,
wo wir sagen, A, soll eine Zahl sein?
B, soll es End-Contract konstruiert einen Vertrag,
der das Number-Prädikat nimmt und dann einen weiteren Test tiert?
Das ein größer Gleichkontrakt, das A, größergleich Ziel sein soll.
Und als letztes sagen wir noch, Ziel soll auch eine Zahl sein.
Und wir sagen, das Resultat soll ein Symbol sein.
Also damit haben wir voll spezifiziert, was übergeben werden darf
und wie die Beziehungen zwischen den einzelnen Werten sein sollen.
Und damit möchte ich dann für heute mal schließen.
Danke für die Aufmerksamkeit