So, hören Sie mich jetzt besser? Okay, also Frage,
was ist beim Entwurf Funktionen zu beachten und was sind typische Idiome und Muster,
die man kennen soll,
mit meinen erstens die Strukturen ins Programm reinbringt,
die andere erwarten, weil sie sie kennen und zweitens,
das meine ich doch, das Rad neu erfinden muss.
Wir hatten mit dem Thema Funktionen höherer Ordnung begonnen, Funktionen als Werte.
Und da kommen wir jetzt nochmal ganz kurz reinschauen.
Wir hatten als Beispiele erstmal besprochen, geben irgendeine Funktion im mathematischen Sinne,
eine Funktion, die über Zahlen definiert ist.
Da kann man sich Gedanken drüber machen, dass man automatisch die erste,
zweite, dritte, vierte Ableitung der Funktion als Funktion erzeugt.
Und unsere erste Funktion höherer Ordnung,
die eine Funktion als Ergebnis zurückgab,
die hat uns dann die Ableitungsfunktion dürf gegeben,
mit der wir in Funktionen übergeben können und bekommen immer die Ableitungen zurück.
Dann haben wir begonnen in den Baukasten reinzuschauen.
Und was wir in dem Bereich schon kennen sollten,
ist, dass wir die Funktion Map und Filter kennen
Und die Funktion Apply. Also das sind,
mit die Arbeitsferte, die man unbedingt kennen muss und die
sie auch jetzt beim späteren Übungsaufgaben möglichst verwenden sollen.
Das Problem des Mapping gegeben, eine Liste von Werten,
wir möchten sie systematisch transformieren, dieselbe Funktion auf jedes Element anwenden.
Und dafür haben wir gesehen,
gibt es sehr praktisch die Funktion Map,
da können wir eine Funktion übergeben und eine oder Megalisten und Map wendet die Funktion darauf an,
erzeugt eine neue Liste der Abbildung der Listenelemente.
Map haben wir
auch gesehen können
Beliebig viele, listen als Argument übernehmen, Einschränkung,
die Listen müssen gleich lang sein und dann werden immer die Listen an derselben Stelle,
Positionen ein, zwei, drei,
vier verknüpft mit der Funktion,
die übergebene Funktion muss so viele Argumente schlucken und vertragen,
wie Listen als Argument übergeben würden.
Also dieses Beispiel hier, wir definieren eine Funktion zwei
hoch als Exponent zwei hoch ein und wenn wir sagen,
Map zwei hoch über eine Liste von Zahlen,
kriegen wir eben die Potenzen der Zahlen zurück.
Und Viele Listen als Argument hatten wir auch gesehen,
wenn wir beispielsweise Plus nehmen und übergeben hier vier Listen.
Plus kann als vierstelliger Operator verwendet werden, als fünf als zehn bis 20-stelliger,
dann wird eben eins plus 10 plus 100 gerechnet,
zwei plus 20 plus 200 und so weiter.
Und wir sehen hier dann jeweils die Stellen addiert.
Wir können Elemente auslisten auf diese Weise zu neuen Listen kombinieren.
Und wenn wir ein Lab mit der Funktion List machen,
über zwei Listen, die als Argument übergeben wurden,
dann können wir eben Weil jeweils sich die Elemente an den gleichen Stellen
in den Argumentlisten nehmen und die verknüpfen zu zweistelligen Listen und können hiermit jeweils den Vornamen und den Nachnamen nehmen und so weiter.
Und sie sehen schon, man kann fröhlich Maps und Filter
und andere Funktionen, die wir noch kennen, Schachteln.
Und das wird eigentlich der typische Programmierstil für die nächsten Aufgabenblätter sein,
dass sie zusehen alles möglichst misshilfe von Funktionen höherer Ordnung zu machen.
Geschachtelte Ausdrücke. Gut, wenn wir mit dem Cons mappen,
dann haben wir praktisch die Funktionen ZIP implementiert
Immer gepunktete Paare erzeugt. Die Funktion filtert, nochmal einen Schritt zurück,
die Funktion weg, gibt uns eine Liste als Resultat zurück,
die genauso lang ist, wie die Länge der Argumentlisten.
Bei der Funktion Filter wollen wir nur die Rosinen rauspicken,
die irgendein Prädikat erfüllen, da bekommen wir eine kürzere Liste zurück,
ist aber auch eine Funktion,
die aus einer Liste eine neue Liste macht.
Im Endeffekt kann sie leer sein,
wenn nichts in der Liste das Prädikat erfüllt und da können wir zum Beispiel,
Filter gibt es als Standardfunktionen in Reggaet,
ja gar kein Problem,
deshalb brauchen wir sie
gar nicht selbst
Zu programmieren, weil man kann sich hier in der Definition
mal anschauen, wie man Filter von Hand machen würde.
Aber Filter gibt es fertig, also können wir es gleich verwenden,
Filter zum Beispiel mit Ott, alle ungeraden Zahlen, Orts findet dann,
wir übergeben diese Liste hier und bekommen dann diese zurück.
Was wurde weggelassen? Die vier und die zwei,
weil sie eben nicht ungerade sind. Das Resultat ist also
kürzer, ja? Genau. An der Stelle hatten wir aufgehört,
wenn wir jetzt Programme mit Funktionen höherer Ordnung zusammenbauen,
dann lohnt es sich, wenn wir Zusicherungen über das Wirken von Filter,
von Map, holt und so weiter verwenden können.
Und Filter hat die Eigenschaften, dass wenn wir zwei Liste unterbinden,
X und Y mit Epent eine neue Liste erzeugen und es
dann mit einem Prädikat P filtern, dass dasselbe rauskommen soll,
als wenn wir erst die Listen filtert und dann die gefilterten Listen mit Epent verbinden.
Also Filter können wir sagen,
ist distributiv bezüglich der Kommengartination von Listen mit Epent.
Das können wir nutzen, wenn wir Code umformen wollen.
Dann kommt dasselbe raus, ob wir zuerst die Teile filtern
und dann verbinden oder erst die Teile verbinden und dann
die Resultatliste filtern
Das funktioniert natürlich nur, solange die Funktionen die Bezugstransparenz waren,
ja. Alle Aussagen dieser Art Eigenschaften,
von die ich hier zu sichere, gelten immer nur Bezug.
Wenn wir Bezugstransparent programmieren und nicht zwischendrin Zustandsänderungen mit Setbang machen, ja.
Also die Funktionen, die wir jetzt kennengelernt haben,
Map-Bild-Listen auf Listen ab gleich lang Filter-Bild-Listen auf Listen ab,
eventuell kürzer, ja, aber nie länger.
Also Filtern fügt nichts hinzu und Jetzt brauchen wir eine Funktion,
die eine Liste nehmen und einen Wert, irgendeinen des anderen Typs rauskriegen.
Und das ist das Falten. In anderen funktionalen Programmiersprachen
hast du diese Funktionen Reduce. Aber in Recket hat man
sich dafür entschieden, dass man sie Fold nennen.
Gibt es in zwei Ausprägungen, Fold L und Fold R.
Was Machtfalten? Wir nehmen die Elemente einer Liste,
beginnen mit einem Startelement und dann verwenden wir dieses Startelement,
das ist meistens typischerweise ein neutrales Element ist und verknüpfen es
mit dem ersten Element der Liste
Und kriegen dann einen neuen Wert heraus. Im nächsten Schritt
wird dieser neue Wert genommen mit dem nächsten Element der Liste
verknüpft und das eben unter Anwendung einer zweistelligen Funktion.
Ja, Fuld braucht eine zweistellige Funktion, die immer das
Neutrale oder Startelement mit dem nächsten Element der Liste verknüpft.
Und die Bezeichnung fuhr L und Fuld App,
zielt darauf ab, wo die Funktion den bisherigen Wert bekommt,
ob sie ihn als erstes oder zweites Argument bekommt.
Bei Fuld L ist das neutrale Element,
das Erste Argument bei Fuld Air, es ist das Zweite.
Genau. Und wir können mehrere zu faltende Listen übergeben,
dann braucht die Funktion mehrere Werte,
aber wir schauen uns mal ein Beispiel von zweistelligen Faltungsfunktionen an.
Okay. Einfach mal ein Beispiel, das man sieht,
wer uns verwendet, wenn wir eine Liste von Zahlen haben
und wollen sie alle verknüpfen, aufsummieren.
Können wir sagen, das machen wir,
indem wir schreiben Fuld L mit dem Startwert Null und dann
die Liste 1, 2, 3 zum Beispiel,
Kommt sechs raus. Oder wenn wir sie alle miteinander multiplizieren wollen,
würden wir schreiben, FultL übergeben die Multiplikationen und nehmen jetzt aber als Staatwert eine Eins.
Dann könnte man ein, zwei, drei rauskriegen.
Oder das Minimum. Also häufig macht man es beim Falten so,
dass man als Stadtwert einfach das erste Element der Liste nimmt
und dann nur die Restliste faltet.
Das erspart einem die Mühe, ein neutrales Element sich zu überlegen.
Also eine geschickte Art, die länger eine Liste herauszufinden,
wenn es die Linksfunktion nicht gäbe,
wäre.
Wir nehmen unsere schöne Funktion mit
Und übergeben fürs Mapping eine Funktion, die jedes Element der Liste auf eins abbildet.
Wir sehen hier, wir übergeben die Funktion,
die nimmt ein Argument nix und bildet es einfach konstant ab auf eins.
Was macht Lab von der Funktion Lamda X1?
Es wird jede Liste in eine Liste von Strichen überführen,
wo wir für jedes Element eine Eins haben.
Also dieser Teil hier würde die Argumentliste 1, 2,
3 abbilden auf 1, 1, 1.
Und wenn wir das jetzt falten, mit Fuld Plus und Null,
dann kriegen wir einfach gezählt,
wie oft steht die Angst hier drin und die Summe
der Einzelnen gibt uns hier als Länge
Wir können frohes Selbst trekosiv, war natürlich programmieren.
Ich lasse es mal im Skript stehen für Sie als Übung,
dass Sie mal drüber nachdenken. Wie könnte ich
das Falten selbst programmieren? Zurzeit gehe ich davon aus,
dass Sie in der Lage sind, eine einfache,
lineare Iteration schon ganz gut und flüssig zu schreiben.
Wir können jetzt aber das selbstgeschriebene Fold einfach mal nehmen,
um es mit Schress zu verfolgen, um zu sehen,
was das Falten macht.
Und dieses My Fold L, wenn wir es aufrufen,
sagen wir zum Beispiel, wir falten eine Liste mit Cons.
Wir übergeben hier die Liste 3-2-
Nehmen als Startelement die Lehrerliste. Und wenden jetzt zum paar Weisen Verknüpfen immer Konz an,
kriegen wir am Ende natürlich wieder eine Liste raus,
die so lang ist, wie die ursprüngliche Liste ist.
Aber wenn sie genau hinschaden, hier in der Regression,
dass sie sehen, dass ein Falten mit Konz die Liste umdreht.
Vorher hatten wir die Liste 3, 2,
1 und was wir zurückkriegen, ist 1, 2,
3. Das heißt, ein Falt folduell mit Konz ist eine Implementation der Reverse-Funktion.
Die dreht die Liste um. Wenn wir irgendwas
mit Fold 11 verarbeiten und Wenden es darauf an,
kriegen wir die auch verarbeiteten Argumente in der umgekehrten Reihenfolge.
Okay. Wenn wir jetzt die zweite Variante Fuld ernehmen,
das Argument wird an zweiter Stelle übergeben.
Dann bekommen wir in dem Fall heraus, dass wir bei Fuld,
die Liste genau in der Reihenfolge zurückkommen, wie wir sie haben.
Und wir haben hier jetzt allerdings keinen Akkumulator.
Also hier haben wir eine entrekursive Funktion gehabt mit Fuld,
bei Fuld,
Air haben wir
Eine allgemein rekursive Funktion. Es ist eine Frage der Effizienz,
ob man Fuld L oder Fuld R nimmt und auch,
ob man die Sachen, in welcher Reihenfolge man eine gefaltete Liste wiederkriegen will.
Gut, also die Reduktionsreihenfolge ist wichtig,
wenn wir nicht kommutative Operatoren übergeben,
wie zum Beispiel Cons oder wenn wir,
wenn die Liste sehr lang ist,
in der allgemeinen rekursiven Fassung,
dann erzeugen wir einen Stack und das Falten kann etwas
dauern,
weil baut einen unnötigen tiefen Aufrufkeller auf
So, jetzt einfach, um ihren Werkzeugkasten zu vervollständigen,
eine Reihe nützlicher Funktionen, die man immer gerne nimmt,
um Werte zu erzeugen.
Also, was ich sehr nützlich finde, ist die Funktion Buildlist,
die erzeugt uns einfach aus dem Nichts eine Liste.
Und zwar hat sie als Argument eine Funktion und eine Zahl,
die sagt, wie oft die Funktion angewendet werden soll.
Was macht diese Funktion? Die ruft die Funktion F auf,
einmal mit dem Argument Null,
einmal mit dem Argument 1 und zum Schluss mit dem Argument N-1.
Und wir bekommen alle Aufrufe von F mit diesen Argument 0
-1-2-3 zu einer Liste zusammengebaut.
Und wenn wir zum Beispiel sagen, Build List 5 und übergeben, die Square-Funktion hier.
Ich habe sie hier mal in Landerformen ausgeschrieben, einfach nur,
damit wir uns mal langsam an die Lamm der Notation gewöhnen.
Diese Funktion hier, die rechnet einfach x-mal x für jedes Argument
X. Ein Build List mit 5 würde uns Die Zahl.
Null mal, null. Eins mal eins, zwei mal zwei,
drei mal drei und vier mal vier geben als Liste zusammengefasst.
So können wir also Listen mit Anwendungen einer Funktion bauen.
Wir verwenden auch Funktionen höherer Ordnung, wenn wir Dinge wiederholt machen wollen.
Also Wiederholungen, die sich so auf Listen beziehen,
da haben wir die fertigen Funktionen Map und Filter.
Wenn wir andere Wiederholungen brauchen, wir wollen irgendwas über
die Menge der natürlichen Zahlen machen oder so.
Da ist ein bisschen Quatsch erst eine Liste aller natürlichen Zahlen
zu bauen und dann darüber mit Map zu iterieren
Es gibt schöne Bausteine wie Itorate, die verwendet werden können,
um praktisch eine Waldschleife oder eine Vorschleife zu machen.
Also wenn wir jetzt sowas aufbauen wollen,
wie die Liste aller Zweierpotenzen, können wir das auch so machen,
indem wir die Funktion Itorate nehmen.
Die kriegt zwei Parameter. Nämlich der eine Parameter besteht darin,
dass wir wie bei dem Build-List eine Funktion nehmen,
die immer wieder angewendet wird.
Und das zweite Argument ist eine Funktion, die die Approchbedingungen darstellt.
Die testet, sind wir schon fertig. Also Iterit kriegt eine Funktion,
ein Endeprädikat, einen Startwert und wendet die Funktion wie das Fold
immer auf den Wert, an den wir gerade hatten.
Und das machen wir so lange, bis das Endeprädikat,
diese Funktion sagt, ja, der Wert,
den ich jetzt habe, erfüllt meine Abbruchbedingungen.
Also ich könnte die Zweierpotenzen auch so machen mit Iterit.
Wir übergeben Als Funktion, die iteriert wird, einfach zweimal X.
Und das ist unser, sei mal unser Iterator.
Und wir hatten übergeben als Endetwert, wenn er unser Ex-X
größer als 64 ist, dann wollen wir aufhören.
Und wir fangen an mit einem Startwert von eins. Und dann kriegen wir hier,
also den Startwert eins als erstes, dann zweimal eins,
dann zweimal zwei, dann zweimal vier und so weiter.
Ja. Und sind fertig. Also dieses Iternate ist
eine kurze, knappe Formulierung für eine Iteration
Die erste Funktion sagt immer, was mit dem bisherigen Ergebnis geschehen soll,
wie es im nächsten Iterationsschritt verarbeitet werden soll.
Und der Endetest sagt, wenn wir aufhören wollen.
Also es ist eine funktionale Art, eine Wahlschleife zu schreiben.
Die Iterationsfunktion ist der Rumpf der Schleife. Und dieser Endetest,
der sagt, wann wir aufhören wollen. Also ich kann jetzt mit Iterit,
meine natürlichen Zahlen, bis zu einer Zahl N einfach so machen,
indem ich die Funktion ad wann,
die zu einer bestehenden Zahl immer eins addiert,
nehme als Iterator und ich höre auf,
X größer gleich Ende ist, ja,
und fange mit eins an zum Beispiel.
Ich hätte, solche Sachen können wir auch ganz gut mit
dem Fold L machen, um mal ein anderes Beispiel zu nehmen.
Wenn ich eine Zeile von N-Leerzeichen haben will,
kann ich es auch so machen, indem ich sage Fold L,
ich nehme als Verknüpfungsfunktions dringepennt und als Startelement des Leerzeichen,
ja, und dann mache ich ein Mapping,
in dem ich über die natürlichen Zahlen bis N einfach Jede Zahl aufs Sterezeichen abbildet.
Sie sehen schon, häufig ist es beim Funktionalen Programmieren so,
dass die Funktionen höherer Ordnung so spannend sind,
dass man viele Möglichkeiten findet, dasselbe zu machen.
Und für sie ist die Herausforderung immer die treffendste und effizienteste Form zu finden.
Wenn sie Skript durchgehen, werden sie rund 40 Versionen finden,
wie man die Länge einer Liste berechnet.
Und der Verwendung schöne, hübsche Funktionen höherer Ordnung.
Aber die Herausforderung an sie ist eben nicht die komplizierteste Lösung zu finden,
die einfach einen hübschen Ausdruck verschachtelt der Funktion höhere Ordnung gibt,
hinterher keiner mehr nachvollziehen kann, sondern die treffendste und knappste,
sodass jeder auf Anhieb versteht, was sie da gemacht haben.
Als ich angefangen habe, Funktionen, höhere Ordnung mit in Skripte einzubauen,
dass es mir auch passiert, dass ich gelegentlich Kurt geschrieben habe,
den ich ein halbes Jahr später nicht mehr verstanden habe.
Denn man schreibt es schnell und hinterher sitzt
man zehn Stunden da und rüber drüber nach,
was habe ich da am Himmelsfilm bloß gemeint. Liegt daran,
dass es eben sehr viele Funktionen, höhere Ordnung gibt,
die ähnliches tun und wenn man sie zu sehr verschachtelt,
kann es sein,
dass man langsam
die Übersicht verliert
Okay. Also immer versuchen, treffend und klar und
das typische Idiom zu finden, dass man auch versteht,
was gemacht wird. Also so schön Funktionen höherer Ordnung sind,
wenn sie einen String aus ein Leerzeichen haben wollen,
ist es meistens Treffende einfach zu sagen,
erzeuge ihn mir einen String der Länge N und jedes Zeichen ist ein Space.
Rekic hat eine nette Struktur, die in Richtung Strom
orientiertes Programmieren geht.
Und das sind Generatoren
Und da schauen wir auch gleich nochmal rein, was ist ein vordefinierten Generatoren gibt.
Die Funktionen, die ich jetzt eingeführt habe,
die sind eigentlich schon komplementär in dem Sinne,
das Iterit, ein Generator ist,
der erzeugt eine Folge von Werten und Fuld L. und Fullair sind Reduktoren,
die Listen mit Werten nehmen und sie reduzieren auf einen Wert,
irgendein Typ.
Es hängt von der Basisfunktion ab,
was für ein Typ dann der herauskommt.
Gutes Iterit können wir
Können wir selbst implementieren, aber das lassen wir mal.
Gucken wir mal drauf, welche Funktionen wir jetzt schon kennen sollten.
Erstmal das Apply. Hatten wir das letzte Mal besprochen.
Apply appliziert eine Funktion auf eine Liste. Und zwar in der Weise,
dass alle Elemente der Liste genommen werden als Argument der Funktion.
Immer dann zu nehmen, wenn das,
was wir als Argument für die Funktion nehmen wollen,
eine beliebige Länge sein kann und wir es nicht ausbuchstabellieren können,
indem wir sagen, K von X, Kadafond X,
Kadafond X und so weiter, wenn wir nicht wissen,
wie lange die Liste ist,
können wir
es nicht
direkt hinschreiben
Deshalb, wenn wir eine Funktion haben,
die beliebig viele Argumente nimmt und wir wollen sie anwenden und kriegen die Elemente als Liste,
dann sagen wir, Apply-Funktion auf die Liste und das ist,
als hätten wir beschrieben, F von erstes Element,
zweites drittes Klammer zu.
Ganz praktisch, wir können wirklich ganz schnell und einfach zum Beispiel
alle Zahlen in einer Liste addieren, indem wir sagen,
Apply plus und die Liste und wir haben sie.
Dann haben wir das Abbilden von Listen gesehen, Liste nimm,
Funktionen anwenden auf jedes Element der Liste, neue Liste,
abgebildete Funktionen rauskriegen. Wir haben Filtern gesehen,
Wir haben Falten gesehen von links oder rechts,
unterscheidet sich einfach, wo wird das Zwischenergebnis genommen,
als erstes oder zweites Argument der Funktionen, die wir übergeben.
Und dann haben wir noch Iterate gesehen und wir können das
auch in einer Variante, in dem Itter an Till,
wo wir übergeben, bis wann iteriert werden soll.
Und wir haben Verträge gesehen, so was wie Flat Contract und so weiter.
Verträge sind Zusicherungen und die werden konstruiert, wie Funktionen
höhere Ordnung. Es sind eben auch Funktionen höherer Ordnung,
die uns diese Verträge konstruieren
So, ein ganz kleiner Blick nur auf das, was mit Generatoren und Sequenzen möglich ist.
Sequenzen ist jede Art von Elementen, die linde ja angeordnet sind. Das können es dringend sein,
Vektoren, Listen,
all das wird in Recket betrachtet als ein Element vom Typ Sequenz und wir müssen den Code nicht unterscheiden,
ob wir jetzt etwas anwenden wollen über Vektoren oder Listen,
wenn wir den Code so schreiben,
dass wir Funktionen verwenden,
die Sequenzen akzeptieren,
dann haben wir schon gewonnen,
dann kann man später mal als Vektor implementieren oder als Liste
und so weiter
Und solche Sequenzen können erzeugt werden, zum Beispiel mit Hilfe von Generatoren.
Ein Generator besteht daran, dass wir einfach sagen,
wir schreiben Generator. Und der kriegt keine Argumente,
der legt einfach los und hat einen Body.
Und dieser Body wird immer ausgeführt. Bis zu einem Punkt,
wo im Code von Body das Generator steht, wehlt,
dann hält er einen Moment an.
Und wenn er bis dahin gekommen ist,
dann hat der Generator für uns den nächsten Wert bereit,
der ermittelt wurde.
Und dann können wir den Generator fragen,
was ist eigentlich dein momentaner Wert
Stellen Sie sich einen Baustein vor, der läuft und läuft und läuft und läuft.
Und immer, wenn er an einer bestimmten Stelle ist,
dann sagt er jillt und dann können wir einen Wert abfragen.
Dann läuft er weiter, berechnet den nächsten Wert und so weiter.
Und von diesem Generator können wir einfach Sachen kriegen,
eignet sich hervorragend, wenn wir etwas einlesen wollen.
Wir warten auf Benutzeraufgabeeingabe und dann kriegen wir die Eingaben und so weiter.
Okay, also wenn wir jetzt uns das mal anschauen wollen,
einen Generator, der uns auf Anfrage die nächste natürliche Zahl gibt,
der zählt also die Welt der Zahlen auf.
Können wir
so machen
Wir definieren eine Funktion Next. Ja, diese Next für
ein Nix. Die kriegt ein X übergeben.
Und dieses X, das wird zurückgegeben mit Yilt,
wenn man den Generator später fragt, was ist dein augenglückliches X?
Und dann ruft sie sich regulsiv wieder auf mit 1 plus X.
So, jetzt kann ich schreiben, die natürlichen Zahlen sind
ein Aufruf eines Generators, der mit Next von 1 beginnt.
Und jedes Mal, wenn ich sage, hier, Generator,
Naps, gib mir eine natürliche Zahl, die nächste.
Beim ersten Aufruf kriege ich eins. Beim nächsten Aufruf
klicke ich zwei, beim nächsten kriege ich drei,
beim nächsten kriege ich vier
Wenn man sich das anguckt, dann sieht man,
das ist super einfach zu benutzen.
Wir schreiben uns einen Generator. Jederzeit, wenn wir aufrufen,
kriegen wir den nächsten Wert, der jetzt interessant wäre.
Wir haben Funktionen aufgerufen. Sind wir hier noch beim reinrassigen
, funktionalen Programmierstil? Wenn wir uns diese Folge von Aufrufen
mal ausschauen. Nee, ich sehe einen Kopf schütteln.
Haben Sie eine Begründung dafür?
Reingepasst, wir haben 15-9 Stile,
weil wir ja eigentlich sozusagen das, was wir beide,
ich wollte sozusagen, was können wir,
wir werden nicht direkt, die zweite Funktionsaufnahme erzählen,
haben wir da mehrmals machen können,
die da selber Ergebnis bekommen, das dann auch versetzen können,
dass es jeder Jahre offensichtlich nicht der Fall ist.
Genau, also dass Sie es ganz deutlich, wir wenden es viermal,
rufen es viermal auf und kriegen vier verschiedene Ergebnisse.
Das heißt, Generatoren sind zustandsorientiert, Sie haben einen internen Zustand
und von dem hängt ab, was man als nächstes kriegt.
Sie sind absolut nicht geeignet für eine reine Wertesemantik.
Bei der Wertesemantik haben wir gelernt und geübt,
dass man gleiche Zeile, gleiche Kurzzeilen ersetzen kann durch das,
wozu wir das andere reduziert haben.
Und das muss bedeuten, dass wenn wir denselben Text hinschreiben,
jedes Mal dasselbe rauskommen, sonst klappt es nicht.
Das heißt, Generatoren sind ein sehr schöner Weg und imperative
Sprachelemente systematisch und einigermaßen robust in eine funktionale Programmiersprache reinzubringen.
Ich werde deshalb die Generatoren nicht so sehr vertiefen
Das werden sie auch nur in ganz wenigen Beispielen anwenden,
weil wir genau an der Stelle einen Bruch haben,
wo wir in die Imperative Sprachwelt gekommen sind.
Wir können aber sowas wie Iterate mithilfe von Generatoren und Sequenzen einfach leicht implementieren.
Wir definieren zwei Stützfunktionen F-Generator. Erzeugt einen Generator,
der einen Staatwert erzeugt und dann Funktionen angewendet auf den Staatwert,
auf F und F von endet.
Und wir können jetzt um Teile der generierten Sequenz zu nehmen,
so was wie Take-Generator an Thöllen nehmen und da können wir
Sprachelemente verwenden,
wie Inproducer oder Falllist
Also hier zum Beispiel ein Generator,
der jetzt hier innen drin rekursiv eine Funktion aufruft und dann jedes Mal in dieser Schleife
die Funktion F anwendet auf das Ergebnis und das macht er so lange,
bis wir fertig sind.
Bis wir sagen, wir haben die Abbruchbedingung erreicht.
Und damit haben wir jetzt eingehüllt, ein imperatives Sprachelement,
der imperative Teil ist verborgen und nach außen sieht es aus,
als wäre es eigentlich eine reine Funktion und wir hätten und dann rein funktionalen Programmierstil erhalten.
Gut, Sequenzen, dieser Datentyp, den sie in Recket finden,
wenn sie jetzt mehr größere Projekte angehen wollen und es verwenden wollen,
der beinhaltet Listen, Vektoren, Hash-Tabellen, Strinks.
Und wir haben für Sequenzen auch ganz nette Strukturen wie Vorschleifen.
Wir können zum Beispiel sagen, einfach vor,
tue das und die Laufvariable ist das I und
die Laufvariable wird aus einer Liste genommen
Eine der vielen Möglichkeiten. Es gibt viele, viele Ausprägungen von Vorschleifen in Reckett,
eine der Möglichkeiten ist es, dass man hier eine Liste übergibt.
Und diese Liste könnte mit einem Generator erzeugt werden.
Und dann können wir darüber, das liegt in einer Schleife mit einer Laufvariable iterieren. Gut,
ich möchte jetzt aber wieder zu dem Teil zurück,
wo wir tatsächlich rein funktional bleiben, keine iterativen oder imperativen Elemente verwenden,
sondern es ist noch ein Beispiel, wo wir Eine Funktion,
höhere Ordnung bauen, die sehr nützlich ist,
Funktionen als Argument bekommt, Funktionen zurückgibt und damit Interessante machen,
Sachen machen kann.
Okay, dafür brauchen wir noch eine weitere Stützfunktion, höhere Ordnung.
Das ist die Funktion Itter an Till. Wir hatten Itterit
kennengelernt, Itterit hat uns eine Liste aller Zwischenergebnisse gegeben.
Wenn uns diese Zwischenergebnisse sich interessieren,
dann wollen wir sie nicht als Liste haben und das Element,
an dem wir am meisten interessiert sind,
das Endergebnis steht am Ende
Sondern wir wollen viel lieber nur das Endergebnis haben und das
macht It der Antil. Das nimmt also die Funktion,
ein Endepritikat sind wir fertig und wendet die Funktion immer wieder
an auf das Zwischenergebnis, was wir haben.
Und wir bekommen am Ende das letzte Zwischenergebnis zurück,
das, was gerade das Prädikat erfüllt hätte.
Wir können damit zum Beispiel eine Funktion Klingelingeling machen,
die in der Terminal-Glöckchen hier Leutetür.
Wir übergeben Einen Aufruf der Funktion Berg. Und zählen einfach,
wie oft haben wir schon geklingelt, es macht dies hier.
Und dann haben wir eine Abbruchbedingung gleich X, wie oft.
Und das, wie oft übergeben wir hier als Parameter.
Das sagt also, wann wir fertig sind und wir starten
hier mit einem Null. So. Und dann, wenn wir
dann klingelingen von 60 aufrufen, sollte das Ding klingeln.
Tut es aber nicht. Na gut. Ich habe auch
kein Audiokabel angeschlossen. Okay. Das verwenden wir jetzt mal
. Also all die Stützfunktionen
Ich Ihnen jetzt erzählt habe, um damit ein etwas größeres anspruchsvolleres Projekt zu modellieren.
Und ich habe dafür mal wieder ein Beispiel,
was so ein bisschen mathematisch angehaucht ist, genommen,
einfach weil das in den Lehrbüchern fürs funktionale Programmieren immer genauer an diesem Beispiel erklärt wird.
Da gucken wir, werden wir jetzt uns also mal darauf stürzen,
ein paar Module zu schreiben und diese Module dann aufzurufen.
Module in Recket sind von der Art, dass wir Dass
jeder Fall damit dem Zeichen lang und in irgendeine Sprache erwähnt,
automatisch ein Modul ist. Also wir müssen ja nicht groß sagen,
du bist ein Modul und so weiter,
sondern wenn wir ein Stück Programmcode schreiben und beginnen damit,
dass wir sagen, in welcher Sprache das verstanden und interpretiert werden soll,
dann haben wir schon ein Modul geschaffen.
Module dürfen nicht geschachtet werden. Das geht zurück auf die
Äußerung von Per Brinchanzen. Das war einer der großen Theoretiker
aus dem Bereich Betriebssysteme. Und der hat für Geschachtelte
Module gesagt,
Nesting ist for Birds
Also Schachteln, Nesting wollen wir nicht. So ähnlich ist
eben Recket auch angelegt. Wir haben keine geschachtelten Module.
Was passiert, wenn wir ein Modul schreiben? Wenn wir
ein Modul deklariert haben, dann wird die Syntax expandiert.
Das Modul wird übersetzt. Und der Modulkörper wird aber
erst ausgeführt, wenn das Modul von irgendjemand gebraucht wird.
Also wenn ein anderes Modul Require schreibt und dieses Require
wird ausgeführt, dann wird letztendlich das von uns definierte Modul
auch ausgeführt
Jedes Modul wird höchstens einmal ausgeführt,
auch wenn viele andere Module es mit Require bestellen.
Es wird nur einmal ausgeführt. Wenn wir im Modul irgendetwas
definieren, dann sind die Namen lokal. Und wir können
sie nach außen sichtbar machen, indem wir sagen, provide.
So ein Provide schreibt man typischerweise an den Anfang des Moduls,
damit jeder sofort sieht, was stellt dieses Modul an Namen bereit.
Okay. Einmal ein Beispiel, wie bei so einem Provide
schreiben kann,
das ist ziemlich kompliziert
Also normalerweise schreibe ich in meinen meisten Programmen einfach rein,
provide A, B, C, wenn das A,
B, C, Namen von Dingen sind,
die zur Verfügung stellen will.
Man kann aber sehr diffizil alles Mögliche bereitstellen,
zum Beispiel alles, was man von den importierten Modulen übernommen hat.
Und man kann Namen zur Verfügung stellen, indem man sie umbenennt in der Schnittstelle.
Also das ist meistens ganz neckisch, indem man sagt,
ich benenne sie um,
damit es außerhalb des Moduls und anderen Namen hat,
als ich hier hatte
Also man sagt, wie heißt das Lokal in meinem Modul?
Und wie soll es heißen, wenn ich es weitergebe?
Das ist dann nützlich, wenn viele ihre Variablen alle gleich genannt haben.
Dann muss man sich irgendwas einfallen lassen, damit das,
was die Leute aus importierten Modulen übernommen haben,
dass es dann unter einem Namen erscheint, wo keine Namenskonflikte entstehen.
Wir können structures über, mit Provide bereitstellen und dann
auch noch sagen, welche Felder wir davon bereitstellen wollen.
Wir können, wenn wir ein Modul importiert haben,
mit Require sagen, dass wir alles aus diesem Modul weitergeben
Oder, dass wir alles weitergeben mit Ausnahme ein paar Dingen,
die wir nicht der Welt verraten wollen.
All from accept, ja. Oder alles, was definiert ist.
Oder alles, was definiert ist, in meinem Modul, außer.
Oder wenn wir es robust machen wollen, ich finde das sehr nützlich,
dass wir einfach einen Prefix einführen für den Namen unseres Moduls.
Und dann beginnen die zur Verfügung gestellten Namen, alle mit diesem Kürzel,
Prefix, Doppelpunkt und sind dann da, ne?
Wir können sie schützen mit bestimmten Arten,
wie sie
geschützt werden sollen
Also das Tools-Modul zum Beispiel,
was wir in der SE3-Bib haben, das stellt die für
diese Vorlesung selbst definierten Funktionen zur Verfügung und beginnt einfach damit,
dass die Namen mir aufgezählt werden.
Conjoine disjoin, some some every. Also alles Funktionen,
die ich hier eingeführt habe und die sind aus dem Betüffel des Tools-Moduls verwenden können.
Und das Tools-Modul selbst zieht Dinge rein wie der Fmakro,
das ist etwas, was wir später verwenden werden,
um eigene
Syntax- und eigene Sprachelemente zu schreiben
Das zieht die, ja, requiriert den Generator,
ja, oder ein. Und dann können wir noch sagen,
wo und das einzige, was aus einem Modul genommen wird.
Also Recket Gui hat zum Beispiel die Funktion Val,
ich will aus dem Recket Gui nichts weiterhaben, als genau das.
Das tut es ein bisschen kalt. Ich denke dann inzwischen
genug frische Luft. Ja, also so können sie
ihre Module zusammenbringen
Das sei denn, wir stellen gut
der Verfügung zum Beispiel Faktives, Demo und so weiter.
In dem Beispiel, wo wir die kochschen Schneeflocken gemalt haben.
Und da steht dann drin, was verlangt wurde.
Ja. Okay. Wir können in den Provide auch die Zusicherung reinnehmen.
Ich habe nicht so ausführlich über Verträge und das Vertragsmodell gesprochen,
aber wenn sie es verwenden wollen,
später mal, wenn sie größere Projekte in Recket machen,
zum Beispiel bei uns, wenn sie in das Praktikum kommen,
dann Können Sie beim Provide auch gleichzeitig einen Vertrag mit angeben?
Und damit ist gesichert, wenn Sie es zur Verfügung stellen,
darf es nur so verwendet werden, dass dieser Vertrag erfüllt ist.
Ja? Das sind alles so mögliche Dinge.
Gut. Das üben wir dann aber im Praktikum,
wie man diese Module aufsetzt, wie man Verträge reinnimmt und solche Sachen.
Kommen wir jetzt mal zum Beispiel Funktionen höherer Ordnung und wir wollen
ein bisschen formal spezifizfizieren, was unsere Funktion tun können soll.
Wir schreiben jetzt mal als erste leichte Fingerübung eine Funktion,
die die Quadratwurzel einer Zahl zieht
Und dann müssen wir ja irgendwie formulieren, was sind
unsere Vorstellungen davon ist, ob diese Funktion korrekt ist.
Und so die intuitive Vorstellung ist, wir haben die Quadratwurzel der Zahl,
wenn das Quadrat der Quadratwurzel die Zahl selbst ist.
Und dann könnten wir also zusichern,
die Funktion Miles Quare Rute, der M's Queroot,
die wir schreiben, die soll immer einen Wert geben,
der größer gleich null ist.
Wir ignorieren hier selbstherrlich, dass die Quadratwurzel auch negativ sein kann,
dass für eigentlich einen positiven und einen negativen Wert zurückkriegen könnten
Und dass das Quadrat, unser Quadratwurzel, dass das gleich X ist. Nun wissen wir schon,
solange wir nicht mit der Genauigkeit der natürlichen Zahlen in Recket rechnen oder der Rationalzahlen,
sondern auch relationalen reellen Zahlen rauskriegen wollen, dann wird das nicht immer klappen.
Und deshalb sagen wir jetzt,
das schwächen wir etwas ab, indem wir sagen,
dass die Differenz vom Quadrat der Quadratwurzel zur Zahl selbst,
dass das kleiner ist als Erziller,
dass dieses Y größer gleich null ist.
Und dass Äpzilon selbst müssen wir noch aushandeln
Jetzt implementieren wir es, so, Algorithmus, der eine geschriebene Spezifikation erfüllt,
wird Implementation der Spezifikation genannt, das wissen sie. Und der Vorgang nennt sich die Implementierung,
ja, und wenn wir jetzt etwas verkaufen oder weitergeben und haben eine Spezifikation angegeben,
dann sollten wir auch beweisen können, dass diese Spezifikation erfüllt wird,
so, in unserem Beispiel Implementation, da fordern wir jetzt erstmal,
also das Quadrat von Exkurs ja gleich null,
die Differenz ist kleiner als Apps, das Apps ist größer null,
wir machen Mal eine Beispielrechnung mit einem Epsilon von 0.001. Und einem X gleich 2.
Und das könnte man jetzt einfach iterativ machen,
indem man sich den Anfangswert aus den Fingern sorgt und damit loslegt und wir sehen,
oh Gott, das ist schon gar nicht schlecht getroffen,
wenn wir diesen Anfangswert hier miteinander mal nehmen,
kommen wir auf den Wert, leicht modifizieren,
letzte Stelle erhöhen, sind wir schon fast bei zwei angekommen.
Und wenn wir es noch einmal erhöhen,
sind wir übers Ziel hinausgeschossen,
also zwischen diesen beiden Werten liegt das Ergebnis und das vergleichen
wir mal mit unserer Forderung.
Und das kommt also
ganz gut hin
Also jede dieser beiden Ergebnisse würde die Zahl,
die Spezifikation erfüllen. Dann nach systematisch zu suchen,
kann man mit dem Newton-Verfahren machen und das ist jetzt das
erste Beispiel für funktionales Programmieren mit Funktionen höherer Ordnung, wenn wir iterative Probleme haben.
Sie kennen sicherlich aus Ihren Mathe-Übungen das Newton-Verfahren in der speziellen
Form für Quadratwurzeln und in der allgemeinen Form und das bauen wir mal nach,
dann haben Sie ein für alle Mal im Recket eine Lösung im Kasten,
wenn Sie das Newton-Verfahren brauchen.
Die spezielle Formel für Quadratwurzeln, die sagt,
wenn man das Nötenverfahren anwendet, gibt es uns einfach diese Iterationsregel,
sei Y, N-Ernährungswert für Wurzel X,
dann kriegen wir einen besseren Nährungswert Y N plus 1,
in denen wir einfach den vorherigen Nährungswert nehmen und dann X durch den Näherungswert teilen und das Ganze halbieren.
Ja, also das muss iterativ immer wieder gemacht werden,
den alten Wert nehmen, X durch den alten Wert drauf addieren,
das Ganze halbieren und wir haben einen neuen Iterationswert.
Und auch das mal nachgerechnet, im Kopf rechnen.
Konvergiert langsam. So, jetzt schreiben wir es mal als Funktion höherer Ordnung.
Und da haben wir jetzt die Teilaufgabe zu lösen.
In jedem Schritt müssen wir den alten Wert neben den Neuen
berechnen, indem wir das hier drauf addieren.
Und wir hören auf, wenn das Ergebnis gut genug ist.
Und das machen wir so. Also,
wir schreiben eine Funktion im Proof Y,
die nimmt den alten Wert,
die jetzt extrem Y drauf und halbiert das
Unsere Abbruchsbedingungen soll sein, wenn das Ergebnis gut genug ist und es gut genug testen wir,
indem wir die Differenz Y-Quadrat minus X bilden.
Und feststellen, ist sie kleiner als unser vorgegebenes Y und
wir nehmen jetzt mal als Erbsilon 0.003, das ist eigentlich egal.
Und diese beiden Dinge, die wir jetzt festgelegt haben,
die Iterationsriegel und die Abbruchsbedingungen,
die erzeugen wir mit der Wiederholschleife Iterate Until, ja,
Itta Until, die Funktionen nehmen wir dafür.
Dafür haben wir sie eingeführt. Das sehen wir hier
im Rumpf,
Itta Antil
Ruft die Funktion im Pruf auf. Die hat immer
den alten Wert genommen, den neuen draus berechnet.
Ich liefere den neuen Wert. Good Inner testet den neuen Wert.
Und der Statwert ist unser X, für das wir die Wurzel haben wollen.
So. Und dann kriegen wir raus, ja,
unsere Quadratwurzel aus 4 ist 2 und dahinter ein ganz kleiner rationaler Anteil noch.
Ich habe mir hier durch die Mühe gemacht,
dass in der Dezenmalnotation zu überführen, wir sehen,
aber gerade in dieser Bruchdarstellung, wie dicht wir dran sind,
also wir sind wirklich nicht genug rangekommen.
So, wie verwenden wir dieses Itat an Till?
Hier der Kern des Ganzen? Wir definieren meist quer mit von Ex-Krieg,
den Ex wird definieren, hier als lokale Größe das Epzilern.
Wir definieren das good enough als lokale Funktion.
Das ist einfach die Funktion, die testet,
ob sie absolut Betrag von dem Quadrat von Y minus X,
ob das kleiner ist als unser Epzilern.
Das Verbessern, es besteht da in diesem Landerausdruck hier.
Diese lokale Funktion im Prof, indem wir sagen, ja,
addiere zum Y X durch Y und teile dir das Ganze durch zwei.
Also diese beiden Funktionen sind hier lokal definiert und wir übergeben
sie an Itter an Till. Und das legt los,
indem es als ersten Schätzwert für die Wurzel die Zahl selbst nimmt.
Und dann iteriert es los und kommt relativ gut an das Ergebnis.
Wenn wir es mit interexakter Ausgabe machen,
indem wir hier einfach exakt nach innen exakt aufrufen,
dann sehen Sie, die Wurzel ist wirklich gut getroffen.
So, die sind eine Eigenschaft,
dass wir tatsächlich die Bedingung gut genug erfüllen,
die führen wir jetzt noch als Vertrag ein
Erstmal als ein Beispiel, dazu definieren, deine Stützfunktionen gut,
ist Wurzel X eine gute Worte zu X.
Und da geben wir unser Epsilon vor und testen hier absolut
von Squer von Wurzeln des Apps.
Kleine Gleich-Apps. Und jetzt definieren wir unsere Wurzelfunktion,
meist Squerroot, dritte Variante, indem wir die Zusicherung reinnehmen.
Und Sie können an dieser Folie schon sehen,
warum ich mich gescheut habe für jede und alle Funktionen im Skript,
das mit Zusicherungen zu versuchen
Sehen, obwohl es nützlich wäre, das zu tun.
Einfach der Platzbedarf, der wächst ziemlich lächer.
Wir haben praktisch den gleichen Platzbedarf wie in der Programmiersprache,
wo wir alles deklarieren müssen. Also hier diese komische Funktion,
Strich, größer als sie, das ist die Variante,
eine Funktion zu definieren, mit Zusicherungen.
Und diese Zusicherungen werden automatisch überprüft. Hier in dieser Klammer
stehen die Zusicherungen an die Argumente und da steht wir,
für X haben wir als Zusicherung und als Vertrag diesen Vertragskonstrukteur stand der Vertrag.
Er sagt, X ist größer gleich null. Und fürs Resultat nehmen wir den Endcontract-Constructor,
der sichert zu, dass das Resultat eine Zahl ist,
ist ja nicht schlecht für eine Wurzel.
Also wir sichern hier nur dazu, dass es eine Zahl ist.
Ich habe es nicht genauer spezifiziert, falls da jemand versucht,
die Wurzel aus einer negativen Zahl zu ziehen,
dann kriegen wir eben eine Rationalzahl raus, ist durch Number abgedeckt.
Also das ist erstens, wir sagen, das Resultat ist eine Zahl,
da kann man sich darauf verlassen. Und zweitens sagen wir,
die Zahl, die rauskommt, die erfüllt diese Funktion Gott,
die wir auf der Folie davor definiert hatten,
die testet einfach, ist das Ergebnis gut genug.
Und wenn wir jetzt
die Funktion definieren