Dann guten Morgen, herzlich willkommen zur vorletzten Runde.
Wir sind an dem Punkt angekommen, wo wir jetzt schon
einige Programmierstile besprochen haben und heute möchte ich erklären,
wie wir es eigentlich schaffen. Für ein Projekt,
viele Programmierstile gleichzeitig zu nutzen, wie man da hinkommt,
festzulegen, welche Sprache man überhaupt nimmt,
sich dann entscheidet,
okay,
was fehlt mir jetzt aus der anderen Welt
und wie bringe ich das in mein Projekt ein
Also wenn man eine Aufgabe vor sich hat,
wo eine Programmiersprache einfach nicht reicht, dann ist man meistens gekniffen.
Das ist etwas, was ich versuche zu vermeiden bei
sämtlichen Software-Engineering-Aufgaben, dass sich mehr als eine Programmiersprache brauche.
Und wenn ich auf eine andere Programmiersprache ausweichen muss,
dann überlege ich mir sehr genau, wie macht man es, welche der Möglichkeiten wählen wir.
Also wenn Sie in die Recket-Welt reinschauen, werden Sie feststellen,
dass das Recket, was wir in dem Programmierpraktiker benutzen,
munter einen C-Code benutzt und Assembler-Code benutzt.
Einfach über das Found-Function-Interface. Ja, wenn wir Hardware anschließen wollen,
dann kommt meistens der traurige Moment, wo man sagen muss,
wie binde ich diese Hardware jetzt ein.
In unserem Bildverarbeitungspraktikum verwenden wir ein Paket,
das heißt Vic Reckett und das ist in CPlus Plus geschrieben
und wir können natürlich nicht direkt CPlus Plus Code von Reckett aufrufen,
aber Reckett bietet für den geordneten Weg so ein Found-Function-Interface und wenn wir Found Function in der Face nehmen,
ja, wunderbar, dann können wir Das sind andere Bibliotheken,
die installiert sind, verwenden.
Nur das Problem ist, bei jedem System-Update, was man anfangen,
sich anzuschauen, welche Systembibliotheken wurden verwendet, man kriegt damit Plattformabhängigkeiten.
Und auf die Dauer ist es etwas, was man eigentlich
ganz gern vermeiden möchte. Der andere Weg ist,
dass man einfach sagt, ach Mensch,
ich brauche nun mal dies und das aus der anderen Programmiersprachenwelt,
aber ich habe es nicht, denn programmiere ich es in dem Programmierstil,
den ich habe.
Und das gibt meistens unverständliche Programme, ist eigentlich auch keine
so gute Lösung.
Und auch nicht die Effizienz
Jetzt ernsthaft feststellen, das, was wir vorhaben,
zu programmieren, das lässt sich am besten im Prolog ausdrücken.
Dann sollte man einen Weg finden, dass man die ganzen abstrakten Entwurf,
zumindest in einem Prolog denkt und sich dann überlegt,
wie verbinde ich das mit der Welt, die ich habe,
Java oder C oder Recket und so weiter.
Gut, und eine Möglichkeit ist es eben,
wenn man festgestellt hat, ach,
mir fehlen nur zwei oder drei Sprachelemente und dann sieht es richtig gut aus,
dass man die Programmiersprache erweitert,
um diese neuen Sprachelemente den neuen Programmierstil.
Und das wollte ich
heute gern zeigen
Schauen uns an, wie wir Recket benutzen, um es
zu erweitern. Die Erweiterung haben wir ja schon benutzt.
Wir haben ein Zehlers benutzt für um generische Funktionen zu haben.
Wir haben Pro-Log in Recket benutzt und da hatte ich schon darauf hingewiesen,
dass wir eigentlich nur zwei Sprachelemente neu schreiben müssen.
Das ist Shird-Makro und das Anfrage-Makro und dann können wir
in Recket voll ein gutes Zielers programmieren.
Dann legen wir einfach mal los
Das andere Beispiel, was ich heute noch zeigen möchte,
ist zurück zur Stromorientierten Programmierung und wir wollen uns
bei der Gelegenheit auch noch dran erinnern,
wann man tatsächlich eine Spracherweiterung braucht und wenn man das Ganze mit einer Funktion simulieren kann.
So, also erster Punkt das Programmieren mit Makros. Das sind,
ich möchte kurz erzählen, wie kriegen wir es hin,
dann schauen wir uns einige Beispiele an,
wie wir die imperativen Sprachelemente,
die in Recket drin sind,
durch Makros realisieren können
Letztendlich stecken dahinter solche Makros. Und was man neben einer
größeren Ausdruckskraft mit Makros gern macht,
das ist vor allem auch ein Weg zur Effizienzsteigerung.
Und damit finden die, sie ist auch in Sprachen wie C und so.
Und leider sind diese Art, ist ein Programmieren mit Makros etwas,
wo man genau wissen muss, was man tut.
Ja, das ist, deshalb werde ich jetzt langsam breit drüber reden.
Thema Falschstricke. Gut. Also erstmal, was sind denn Makros?
Ich mute, Sie haben Sie in anderen Programmiersprachen schon benutzt.
Im Wesentlichen sind es Schablonen
In denen wir ein neues Sprachelement einführen. Wir sagen,
wir möchten mit unserer Grammatik der Sprache die Syntax- und Semantik erweitern,
etwas ausdrücken, was die Sprache bisher nicht sagen kann.
Wir haben gemerkt, ganz zu Anfang,
als wir über die verschiedenen Auswertungsmodelle gesprochen haben,
dass wir nicht alles als Funktionen machen können,
zum Beispiel Fallunterscheidungen, wenn wir das versuchen,
als Funktion zu machen, hat man eine Übungsaufgabe zu definieren,
eine neue Funktion,
die IF heißt oder My IF,
dann werden erstmal
alle Argumente ausgewertet
Was wir gar nicht haben wollen, denn es sollte eine Kontrollstruktur sein.
Sie sagt, erst das auswerten, dann das oder das und so weiter.
Also wir brauchen eine neue Syntax, die eine andere Auswertestrategie hat.
Und genau das können Makros machen. Das, was man
in Recket wirklich nehmen würde, anstelle von Makros, wie er die fallen Syntax.
Aber ich habe schon gesagt, das ist ziemlich aufwendig.
Diese hygienischen Makros zu verwenden. Wir schauen uns einfach mal
die Billiglösung an Lernen dabei, warum man die anderen braucht.
Und wenn sie wirklich hingehen müssen, in Recket Sprachen erweitern,
dann fragen sie ihr Gewissen und gehen dann vielen Manual-Seiten für hygienische Makros durch.
Aber die Grundidee ist in allen Fällen das Gleiche. Ja,
wir definieren eine Schablone und das Makro hat einen Namen
und bekommt Argumente und dann schreiben wir einen Rumpf,
in dem drin steht, was rauskommen soll,
wenn jemand diese Schablone aufruft und um dieses,
was rauskommen soll zu erzeugen, haben wir einen Sonntagstransformer.
Und das ist das eine. Erweitern der Sprache. Das andere ist Effizienzsteigerung in Sprachen,
die übersetzt werden oder wenn wir unser Record übersetzen lassen,
dann können wir das Makro dafür verwenden,
dass wir die Effizienz verbessern und Teile,
die nur einmal gerechnet werden, braucht, schon zur Übersetzungszeit auswerten.
Dann stehen sie im übersetzten Kot und kosten zur Laufzeit eben
keine Rechenzeit mehr oder Speicher oder was immer gebraucht wurde.
Das ist ein Hauptgrund, weshalb man in den Zählverwandtensprachen Makros verwendet.
Man möchte eine Berechnung von der Laufzeit zur Übersetzungszeit verlagern.
Und wenn wir es vor der Frage stehen,
Makro oder nicht, also das erste sollte recht lange dauern.
Der Punkt überlegen, ob wir es wirklich brauchen
Und wenn wir dann sicher sind, ja,
das geht mit Funktionen nicht, dann entwerfen wir die Syntax,
was wir gerne sagen möchten,
was der Computer zurzeit noch nicht versteht,
weil es in der Sprache einfach nicht drin ist,
dann beschreiben wir auch wieder expandierte Kut aussehen soll und dann
definieren wir es mit Dev Makro und dass diese Definition mit Dev Makro enthält den Transformer,
der die Argumente des Argumenten des Makros nimmt und sie in die Schablone für den expandierten Kot einsetzt.
Und das, was dann rauskommt, 14 Laufzeit laufen.
Aus Peter Norwegg ist ein typischer Spruch aus seinem Buch,
was sich bei der Gelegenheit gerne mal einführen möchte.
The first step and writing a macro is to recognize that every time you right one,
you are defining a new language that is just like liss,
wie hat es für das geschrieben, accept for your new macroph.
The program or who think that wayble rightfully be extremely
frugal in defining macrophs. You sides than summer nahes,
what did you do today, it sounds more appressive to say,
I defined in new language and road to compiler for it,
then to say,
I just tech that a couple of macroph.
Ja, es ist das, was wir tun,
wenn wir Macros einführen.
Wir definieren eine neue Programmiersprache und die Compiler
Man will die Welt, die ohnehin schon tausende von Programmiersprachen
hat, nicht bevölkern mit tausend Varianten von den anderen.
Warum nicht? Introducing immacroputs much more memory strain on the reader
of your program than does introducing a function variable or data type.
So it took not built taken lightly. Introduce maxus onlibender
is a clear need and went the mecrofits in vour with your existing system.
As CER, horre put it. Wann thing the Language
Designer, should natur is to include,
untry ideas office own
Das war gegen die schöne Programmiersprache Ada gesagt worden.
Die hat mich ziemlich viele Merkmale, die bisher nicht erprobt waren,
die neu waren. Und Hor war der Meinung,
die Sprache ist gefährlich, weil es zu viel ist.
Und gare Ideen da drin steckten. Okay,
machen wir jetzt mal das MyIf,
was wir zu Anfang in den Übungen besprochen haben.
Unsere eigene Implementation eines Ifs im Racket. Und definieren es
als Makro. Was wir gern hätten,
und das geht eben mit der Funktion nicht,
wenn wir MyIf schreiben,
dann soll eine Bedingung da stehen und dann soll es
einen Ben-Fall und einen Arztfall geben
Und das soll expandieren zu einem Code, der aus einem Kont besteht.
Und da steht die Bedingung drin. Und nur wenn die Bedingung war,
es soll das Ven ausgeführt werden, andernfalls das dann.
Also das darf Makro morgen wieder aus Zwindel-Set. F,
aus dem Paket und zünde Misk. Death Makro. Wir definieren.
Mein If hat die Argumente Bedingung, wenn und sonst.
So, und was zurückkommen soll, ist der Code.
Dieser Code, hier kommt Bedingung, wenn dein Ernst.
Ja, und wir erzeugen wer den,
indem wir einfach
eine Liste erzeugen
Die Liste. Die Listen sehen ja syntaktisch genauso aus wie
anderer Kurt auch. Und wir basteln uns das als Liste zusammen.
Also schreiben wir List. Da kommt erst mal fest,
das Symbol kommt rein. Dann kommt eine Unterliste,
die enthält die Bedingung und das Wen,
das ist dies Teil, was wir hier da bauen.
Und dann eine zweite Unterliste, die beginnt mit Earls und im Sonstfall.
Und dieser Code, der wird eben nicht direkt ausgeführt,
sondern erst, wenn er zusammengesetzt ist, dann wird,
wenn der Makrotransformer fertig ist und diese Liste erzeugt hat,
dann wird es ausgeführt
Und wir sehen jetzt, dass Auswerten von wenn und sonst
es verzögert und hängt davon ab, ob die Bedingung erfüllt ist oder nicht.
Weil jetzt steht es an einer Stelle, wo es sicher
ist und nicht sofort durch die vorgezogene Auswertung ausgewertet wird.
Das Problem ist, mit dieser komischen Listaufrufe da drin.
Und deshalb ist es seit den Anfängen von Liz üblich,
das ist parallel zum Quot,
mit dem wir eine ganze Liste quotieren,
dieses Quasikquot gibt.
Das ist das Quartierungszeichen,
was so geneigt ist
Wenn wir das machen, dann können wir eine Liste schreiben,
wo alles quotiert ist,
außer den Stellen, an denen ein Komma davor steht.
So, dieses Komma hier sagt,
nimm die Bedingung und setze den Wert der Bedingung textuell ein.
Nimm den Wendfall und setze das hier direkt ein.
Nimm den sonst fall und setz ihn hier rein.
Also da sollen die übergebenen Argumente rein und nicht die Worte, wenn und sonst.
Und das passiert hier eben, indem wir das quasi Quote nehmen.
Und ich finde, so ist dieser Makrotransformer sehr schön zu lesen.
Hier steht im Wesentlichen die Schablone, die wir haben wollen.
Das Wort kommt mit den beiden Varianten. Und die Argumente an dem Stellen eingesetzt,
also die Werte an die Bedingung, wenn und sonst gebunden sind,
an den Stellen, wo sie gebraucht werden.
Wir können also unser eigenes If zum Beispiel so schreiben,
dass wir nur ein Ven machen, eine einseitige Verzweigung,
ja, ein Mai, wenn, also das hat erwähnt,
nur den Fall für den Wendfall,
dass es wahr ist und für den Volksfall nichts oder das Gegenteil,
das Anläss, selbst programmiert, das Anläss gibt es ja auch,
wenn die Bedingung nicht erfüllt ist,
dann wird hier der Rumpf eingesetzt.
So musst du doch erklären, dass so ein Klammer-Affe mit Body dahinter bedeutet.
Das ist auch ein weiteres Element aus diesem Quasi-Quote.
Wir haben hier schon gesehen, dass wir immer,
wo ein Komma steht, Ausdrücke reinsetzen können, die ausgewertet werden,
um zu berechnen, was hier wirklich stehen soll.
Und dieser Klammer-Affe-Firm-Variablen-Namen, das nennt sich Splice. Also wenn
wir hier eine variable Body haben und Setzen Sie hier mit Kommas Bleis rein,
dann wird erwartet, dass hier eine Liste rübergekommen ist und die Elemente der Liste werden der Reihe nach hier reingesetzt.
Also damit packen wir eine Liste aus und setzen alle ihre
Elemente genau an diese Stelle, werden sie reingefädelt.
Und wir haben hier jetzt mit dem Beginn eine Folge von
Elementen aus dem Body, die der Reihe nach ausgeführt werden.
Also dass die Syntax fürs Quasiquot und wenn man das verstanden hat,
einmal diese schräge Quote,
dann die Kommata und dann als Ultramaßnahme noch dieses Bleis hier,
dann kann man loslegen mit den Makros
Und zum Beispiel auch die Waldschleife, die sie finden,
ist im Wesentlichen mithilfe dieser Makros erzeugt.
Also wenn wir uns überlegen, wir wollen jetzt eine imperative Anweisungsfolge haben mitten.
Weil, solange die Bedingung wahr ist, soll diese Folge
der Anweisungen ausgefüllt werden. Können wir das so schreiben,
dass wir hier mit Ledge Reck eine kleine Funktion definieren,
eine lokale Funktion, die soll Loop heißen.
Und was macht die?
Die macht ein Wenn
Solange der Test erfüllt ist, den wir hier im Wallen oben übergeben haben,
soll die Folge Expression, wann du und so weiter ausgeführt werden.
Ja, das macht dieses Lamnder hier. Und dann rufen wir
es hier unten einfach auf. Und weshalb der Dreck,
weil wir ein Lett haben,
bei dem wir eine neue Funktion definieren und die soll sich selbst rekursiv aufrufen,
da reicht nicht mal mehr der Stern,
sondern wir müssen im Lett sagen, es ist etwas,
was sich rekursiv selbst aufruft, weil es gebraucht,
es wird schon gebraucht, wo es selbst definiert wird.
Also die Syntax, die wir hier geschrieben haben, rauskommen soll,
soll so aussehen, ein Loop mit einemeinander und diesen Events.
Und wie kriegen wir es mit Death Macro hin?
Unser eigenes Mai, weil, können wir jetzt so schreiben,
das ist ja das Argumenttest und hat einen Body, ja?
Dieser Body, wenn da und und Rest steht,
dann können wir hier viele Werte hinschreiben als Argumente.
Und die werden zu einer eigenen Liste zusammengefasst und diese Liste heißt hier Body.
Ja, jetzt beim Ledgerek, schreiben wir also,
um das hier zu erzeugen, wieder mit dem Quasiquot-Loop
Die Funktionen, wie sie ausgeführt werden soll,
ein Komma vor dem Test, weil da soll der Test eingesetzt werden.
Und hier verwenden wir wieder das Bleis. Und Body,
dass das, was hier in und Rest zusammengebastelt wurde,
zur Analyste, hier wieder schön auseinandergenommen im Einzelnen ausdrücken dasteht.
Und dann wird Lup aufgeruft und wir haben unsere schöne Waldschleife.
Ja, mit dem, was wir eben gemacht haben,
haben wir schon eine ganze Reihe imperativer Sprachelemente in die reine funktionale Welt eingeführt.
Und nach demselben Schema können wir Ein Weil, ein Until,
ein Doot-Times, ein Doo-List oder solange es keine Fehler gibt,
das ist eigentlich mein Liebling fürs Aus.
Kann man sich die Fehler-Wapp fragen und noch ersparen.
Und dann all diese vordefinierten, imperativen Sprachelevente finden wir ins Windelmüskler,
sind sie schon fertig. Also wenn sie lange Imperative Co-Teile im Racket-Programm brauchen,
ziehen sie einfach den imperativen Teil aus Windelmisk da rein zum Beispiel.
Schauen wir uns mal an, wie wir es verwenden können. Also wir definieren hier der Waldschleife,
eine kleine Variable E. Und solange,
weil während ihr kleiner als Sinn ist, erhöhen wir eh,
hier werden wir richtig imperativ und schreiben das E aus und dann kriegen wir die Werte 8,
9, 10 und 10 ist der letzte Fall,
der noch geschrieben wurde.
Ja, das ist die Waldschleife, wie sie so funktioniert.
Neben neuen Sprachelementen hatte ich schon gesagt,
wir können mit dem Makros eben zur Übersetzungszeit Berechnungen vorwegnehmen.
Lohnt sich natürlich nur für Code, die wir tatsächlich kombilieren.
Solange wir in Recket immer imperativ arbeiten, haben wir nicht viel davon.
Aber wenn wir ein fertiges System haben und wir übersetzen es,
damit mit optimierten Einstellungen, mit effizienten Kurt kriegen,
dann helfen Makros sehr schön.
Also Beispiele, wo das Sinn macht, wenn wir einfach schreiben,
längst der Liste 1, 2, 3. Die Längsfunktion muss die Liste durchgehen und zählen.
Und wenn man das immer wieder macht, in der Schleife,
die 10 Millionen mal durchlaufen wird, dann tut es irgendwann weh.
Oder wenn wir immer Pi mal 2 rechnen müssen, die Multiplikationen unnötig machen.
Also solche konstanten Ausdrücke, die verladert man typischerweise im Makros,
dass sie zur Übersetzungszeit bei der Syntax-Transformation Ausgewertet werden.
Also in dem Moment, wo wir hier,
komm mal, Test schreiben, ja,
da wird ja der Test wie Malpy 2 eingesetzt und ausgewertet.
Und dann haben wir ihn schon. Deshalb, also gucken
wir uns mal an, wie wir das machen können und ausprobieren.
Sagen wir, schreiben wir einmal die Funktion. Everage hier als Makro,
ja, ein Mittelwert berechnen für eine Liste als Makro.
Und wenn wir hier jetzt eine Liste übergeben, ja,
dann passiert, zur Auswertezeit, folgendes,
um den Mittelwert zu berechnen,
müssen wir die Länge der Argumentliste bestimmen und wir müssen die Argumente alle addieren und durch die Länge der Liste teilen und dann haben wir den Mittelwert.
Und das geschieht hier im Spleis, werden hier alle Argumente,
die da reinkommen, reingesetzt, so, 1,
2, 3, 4, 5, 10,
20 und so weiter.
Das wird hier addiert und schon ausgewertet und die Summe steht hier drin.
Und dann muss es noch geteilt werden durch die Länge
der Elemente, wie viele Argumente haben wir eigentlich übergeben
Gut, jetzt testen wir mal, wie lange es dauert,
wenn wir diese Average, diese Average als Funktion schreiben würden.
Also, dann würden wir machen ein Applied Plus auf die Argumente,
die hier übergeben worden sind. Bestimmen die Länge der Argumente.
Und wenn wir das jetzt eine Million mal ausführen lassen,
also mit der Time-Funktion messt, wie lange es dauert,
do it times, eh, ein Millionen Mal,
Average von diesen zehn Zahlen,
dann sehen wir,
wir haben eine CPU-Zeit von
Und, Moment, das war ja mit der Makro-Version,
wenn wir dasselbe mit der Funktionszeit machen, dann sehen wir,
wir haben hier knapp 3000 Sekunden, ja.
Also das Makro ist in dem Fall sechsmal schneller und wenn
wir die Liste sehr viel länger gemacht hätten,
dann würden wir deutlich mehr zu Gewinn kriegen.
Also mir ist sogar noch eine ganze Menge Garbage Collection Time
dazu gekommen, die hier völlig weggefallen ist. Ja,
aber die Fallstricke, also was kann uns so passieren,
wenn wir ein bisschen unachtsam mit Makros umgehen
Also für manche Sachen muss man einmal nachdenken und an einem,
das klar ist, dann stolpert man nicht über diese Fallstricke, ja.
Also das Wichtigste Problem, was wir kriegen können beim Makro,
das sind unbeaufsichtigt, unbeabsichtete Mehrfachauswertung, gibt Brechenaufwand,
kann aber auch Seiteneffekte erzeugen.
Und dass wir unbeabsichtigt die falsche Variable verwenden.
Das ist wirklich tückisch. Also erstmal der unbeabsichtigte Mehrfachaufwand,
das haben wir schon gesehen als ein ganz allgemeines Problem der
verzögerten Auswertung, wie wir es in der Lazy-Evaluation haben,
Wir berechnen hier die achte Potenz von irgendeinem Mix und schreiben
das Makro einfach so. Na gut, von dem die Multiplikation,
am Multiplizieren, das x-acht Mal mit sich selbst,
indem wir es achtmal hier reinsetzen.
Wenn wir jetzt als Argument die Sinusfunktion übergeben,
dann wird hier an jeder Stelle Sinus stehen und wir rufen,
sage und schreibe achtmal diese trigonometrische Funktion auf, was verspenderisch ist.
Wenn wir das mal testen, auch wieder eine Million Mal
machen, achte Potenz von Sinus 1.
Mit dem Makro
Bekommen wir hier... Moment, eine CPU-Zeit von über 3000 Millisekunden,
ja, und auch noch eine hohe Garbage-Collection-Time.
Und wenn wir es als Funktionen machen würden,
dann haben wir hier deutlich gespart. Also,
dasselbe Argument und mehrfach reinschreiben und uns nicht davor schützen,
dass es mehrfach berechnet wird, ist schon mal unwirtschaftlich,
ja.
Die Seiteneffekte sind besonders schlimm bei der Mehrfachauswertung
Wenn wir jetzt nochmal überhaupt diese Inkrementfunktion hier,
die eine Variable nimmt und den Wert erhöht,
als Makro schreiben, könnten wir das so machen,
dass wir sagen, beginnen in X, ja.
Und dann setzen wir hier, übergeben wir hier X,
geben X als Wert zurück. Wenn wir,
wenn ich das, was macht es,
es erhöht den Wert X und gibt die Referenz auf die
Variable X zurück und die Variable X hat sich verändert.
Das ist das Inkrementform aus Windel. Wenn wir das jetzt machen,
X setzen wir auf eins und verhöhnes,
dann kriegen wir erwartungsgemäß den Wert 2 zurück.
Das ist also alles normal. Jetzt nehmen wir unser Makro
, vierte Potenz von X. Hier wird x-4 Mal eingesetzt.
Jetzt rufen wir das auf, etwas mutig mit dem Argument Ink FX.
An jeder Stelle, wo hier X steht,
wird es ausgeführt mit den Seiteneffekten, die es hat,
es den Wert X verändert.
Das heißt, das erste Mal, wo hier X eingesetzt wird,
wird F von X Stern gerechnet.
Danach hat X seinen Wert verändert, dann wird der nächste Ausdruck ausgewertet,
dann der nächste,
dann der nächste
Hier wird der Wert von X insgesamt viermal erhöht.
Und wir, das wird X, der zurückkommt,
am Ende ist sechs, ja. Und das heißt,
es ist mit einem Wert X von sechs
gerechnet und nicht mit einem Wert von X von zwei.
Also ein dramatisch falsches Ergebnis. Einfach weil das Argument,
was wir übergeben haben, Seiteneffekte gemacht hat.
Also an der Stelle sieht man nochmal,
wie wichtig die Bezugstransparent es ist und dass das Verletzen
der Bezugstransparenz uns am härtesten trifft,
wenn wir eine verzögerte Auswertung haben
Also was passiert ist,
wenn wir hier zusätzlich noch eine Funktion Show X definieren,
ja, die nimmt einfach den Wert X und gibt ihm wieder zurück.
Also wenn wir hier Power M mit Show von X machen,
dann sehen wir die Folge.
Zuerst wird zwei, drei, vier, fünf genommen als Experte.
Wir kriegen 120 raus. Und besonders spaßig wird es,
wenn wir an solche Funktionen einen Aufruf von Rendem übergeben.
Der Wert verändert sich bei jedem Aufruf und wir haben überraschende Ergebnisse.
Also Rendem als Argument übergeben an eine Funktion,
von der man nicht weiß, dass sie nicht bezugstransparent ist.
Das kann böse nach hinten losgehen. Den Fehler habe ich
als Erstsemester gemacht und nie wieder erhoffe ich.
Das ist vielleicht, wenn einem nicht klarer ist,
wie Argumente ausgewertet werden, passiert es ganz leicht.
Und zu dem Zeitpunkt hatte ich eben wirklich nicht verstanden,
dass die schöne Programmiersprache Algo 60 ein Call bei Nim machte,
als ich ein Random übergeben hatte als Argument.
Dann macht es genau das. Jedes Mal,
wenn das Argument genommen wird, wird es neu ausgewertet,
was wir hier mit den Makros gemacht haben.
Also Call By Neme ist ziemlich genau das, diese Falle,
die wir typischerweise in Programmiersprachen haben, wenn wir mit Makros rechnen.
Okay, ja. Endtimes, ja, macht wohl,
dass etwas mehrfach macht. An dem Beispiel können wir auch
nochmal sehen, wenn wir hier zum Beispiel Push verwenden,
um etwas auf Xes zu tun.
Und wir führen das ziemlich oft aus. Dann sehen wir
genau dieselben Probleme. Hier haben wir jetzt noch einen
zweiten kleines Problem
Wir definieren hier eine globale Variable. Die feinen I10 und
ich habe sie hier mal nicht in Sterne eingehüllt,
damit man nicht auf den ersten Blick sieht,
dass eine globale Variable ist.
Was wir hier tun wollen, ist, dass wir diesen
Wert von I fünfmal mit Push auf eine Liste X schmeißen.
Ja, das sollte eine Liste geben, die fünfmal den Wert 10 vorne hat.
Und dahinter kann schon irgendwas anderes stehen. Aber schauen Sie
jetzt mal ganz hier oben hin, dass Makro-Endtimes,
was wir hier geschrieben haben, hat eine lokale Laufvariable namens I. So.
Und wenn wir jetzt Folgendes machen, Wir rufen End-Times auf mit dem Wert von fünf.
Fünfmal soll es geschehen. Und übergeben als Argument,
Show, Inkrementform e, dann erwarten wir doch eigentlich,
sollte man meinen, dass Inkrementform von ihr genommen wird,
und zwar das ihr, was wir hier oben definiert haben,
das zurzeit den Wert zehn hat zum Beispiel.
Ja, und was wir jetzt aber sehen, ist,
dass die Laufvariable Die, die wir hier im Makro haben,
dummerweise denselben Wert, wie die globale Variante, denselben Namen hat,
wie die globale Variable E, die wir eingeführt haben.
Und die globale Variable, die erhöhen wir in jedem Schritt um eins.
Das heißt, die Laufvariable, die wir hier in unserem
Dutimes-Makro haben, namens E. Die wird zusätzlich durch das,
was das Makro macht, hier erhöht. Nicht nur,
dass die Laufvariable immer um eins erhöht wird,
sondern das Inkrement erhöht es noch zusätzlich um eins,
weil dieses Inkrement Form mit einmal hier in einem lokalen Kontext
steht,
wo das E was anderes meint
Und deshalb sehen wir, dass die Laufvaria pläiert, die Werte 1,
3, 5. Gucken wir uns das nochmal an, was passiert.
Solche Fehler versteht man am besten, wenn man ein Makro
expandieren lässt. Ja, wenn man Makro definiert,
gibt es die schöne Form Expand Makro, die schreibt einem erstmal hin,
was man da geschrieben hat, wie das Endergebnis aussieht.
Und dann sieht man, hoppla,
ich habe hier ein Do-Times i5, und da steht ein I,
und das ist jetzt aber im Scope,
wo wir hier das I als Laufvariable haben,
und das wird nicht das globale I verändert,
sondern das Lokale
Das ist eigentlich die größte Tücke dabei, dass man bei der Makroexpansion,
wenn wir die Argumente nehmen und die Argumente haben wir geschrieben und gemeint,
die sollen sich auf den globalen Kontext beziehen, den wir zurzeit haben.
Die werden in ein Body reingeschoben und in dem können neue lokale Variable definiert sein.
Und wenn die denselben Namen haben, wieder Text,
den wir da unten reinkopfen, dann steht das mit einmal,
der Text in einem anderen Bindungskontext und wird ganz anders ausgewertet.
Und deshalb müssen wir es hinkriegen, wenn wir ein Makro haben,
in dem wir neue lokale Variable einführen,
dass wir eine lokale Variable verwenden, von der wir wissen,
dass sie nirgendwo im Programm vorkommt.
Auch nicht in den Paketen, die jemand anders geschrieben hat,
die unseren Makro aufrufen werden.
Das erfordert eine extreme Disziplin im Projekt, man muss Verträge schließen.
Susi, deine Variablen, egal was du hast,
lass sie alle mit Susi beginnen.
Und ich werde keine Variable einführen, der Name nicht mit Susi beginnt.
Und man muss sich dran halten und darf es nicht vergessen.
Also das ist ganz schön schwierig, vor allem man solche Pakete weiterkauft,
dann muss man auch nur mit den Kunden verhandeln,
wie die ihre Variablen nennen wollen,
vielleicht Plushio,
der Fluffy
Und man hat irgendwann angesehen, das ist etwas,
was man den Benutzern von Paketen nicht zumuten kann,
dass sie Variable verwenden, dass sie keine eigenen Variablen verwenden dürfen.
Also bleibt uns nur übrig, dass wir zurzeit,
wenn wir eine neue Variable im Makro brauchen, nachfragen,
welche Variablennamen sind schon in Verwendung.
Und dann einen neuen Variablennamen uns künstlich erzeugen lassen,
der garantiert verschieden ist von allen Variablennamen,
die zurzeit im Programm
bekannt sind
Und dafür ist die Funktion Gensom da. Genderate Simble.
Die liefert neue Namen, die zurzeit für das Programm geschrieben wurde,
nirgend und wo es läuft, wo das Makro ausgeführt wird.
Wenn wir dann mit Gensom sagen, bitte,
bitte einen taufrischen, neuen Variablen namen,
den noch nie jemand verwendet hat in diesem Programm,
dann denkt es sich einen aus,
die sind ziemlich einfallslos benannt, die heißen G681 oder so.
Ist nichts Spannendes dabei. Aber die verwenden wir dann,
diese Symbole, um daraus unsere lokalen Variablen zu machen.
Also wir brauchen Eine neue Variable,
die wir als Index verwenden wollen und eine neue Variable,
die wir als Zähler verwenden wollen, ja,
in unserem neuen Dood Times, ne?
Und dann sagen wir, okay, lokale Variable Index,
die wir hier haben. Mit Jensen binde dich an einen Wert
. Times binde dich an einen Wert. Und hier,
da macht es nichts aus, wenn die benutzt ja
schon tausendmal die Worte Index und Times verwendet haben, ja.
Denn diese Namen tauchen nicht im Makro-Code auf, ja.
Unser Makrotransformer hat vor den Namen Times und Index, Dann solche Anquote.
Und da setzen wir dann unseren Gensom-Wert rein.
Und wenn wir das dann laufen lassen, dann geht es gut.
Okay, also das ist die größte Falle, weil der Makro-Programmierung,
dass wir Namen aus einem Kontext in einen anderen reinbringen.
Und das ist das, was wirklich besser gelöst ist,
den hygienischen Makros direkt getat, wenn sie sich mal anschauen,
wie das funktioniert, wenn sie die feinen Sonntags verwenden.
Mit den feinen Syntax können sie sich Namen ausdenken und es wird sichergestellt,
dass sie damit nicht diese Scope-Probleme kriegen, die ich eben angesprochen habe.
Das ist das, wo die fallen syntax vorher schützt.
Und wenn man wirklich jetzt eine lisp-ähnliche Sprache erweitern will,
mit vielen Elementen, also das Aufwändige sich überlegt, dann lieber nicht,
dass Steph Macron nehmen, sondern dann stürzen sie sich auf die,
die feinen Syntax-Literatur und schauen sich das an, wie man das wirklich macht.
Gut. Kommen wir jetzt mal auf die Stromorientierte Programmierung.
Ja, genau. Wir hatten ja schon gesehen,
ganz zu Anfang des Semesters, als wir drüber gesprochen haben,
was eigentlich der Unterschied zwischen den Sprachen Lesi und Racket.
Im einen haben wir verzögerte Auswertung. Im anderen haben wir
vorgezogene Auswertung
Die Dinge, die wir bisher verwendet haben,
haben im Wesentlichen die vorgezogene Auswertung verwendet.
Aber ich möchte Ihnen gern zeigen, wie der die Stromorientierte Programmierung,
die Sie in anderen funktionalen Sprachen funktioniert und wie man das nachmachen kann.
Eigentlich, was der Ansatz ist, wie man die Sprache Lese implementiert hat.
Wir brauchen in der Stromorientierten Programmierung verzögerte Auswertung.
Das ist zum einen ganz wichtig, damit wir unendliche Listen
als Argumente weitergeben können, potenziell und endlich große Datenstrukturen.
Und da kommt natürlich ein Endlosschleif, wenn man die mit vorgezogener Auswertung verarbeitet.
Und dann sind typischerweise in diesem Programmierstil Listenströme und für sich
ein Strom von einer normalen Liste unterscheidet,
das wollen wir uns auch nochmal genauer anschauen.
Also wir können verzögerte Auswertungen gut verwenden, wenn wir nicht strikte Funktionen haben.
Sowas wie das End- oder Ohr in Recket. Das sind nicht strikte Sprachelemente,
weil man mit der Auswertung der Argumente aufhört,
wenn man schon feststellt, er hat das Ergebnis in einem End,
das Ergebnis kann einfach nicht mehr truh werden,
weil eines der Argumente schon False war oder beim Ohr
Es ist schon wahr und kann nicht wahrer werden,
indem ich die anderen Alternativen auch noch durchrechne.
Das sind nicht strikte Elemente in Reckett und im Lesy-Programmierstil
sehen die genauso aus wie die anderen Funktionen auch.
Wir können unnötige Berechnungen vermeiden, wenn wir Dinge zurückstellen,
bis sie wirklich gebraucht werden.
Und unendlich große Datenstrukturen. Also Ströme sind sehr beliebt.
Grande, Heske, da ist jede Liste automatisch ein Strom,
eben auch in Lesy in Reckett.
Und die Pipes in Unix sind Ströme,
bringt Spaß
Typisch für die Ströme ist, dass wir uns vorstellen,
wir haben eine Verarbeitungskette.
Am Anfang stehen Generatoren, die erzeugen uns Werte.
Und diese Generatoren können potenziell unendlich viele Werte schaffen.
Wir kümmern uns immer nur um einen zurzeit. Aber auf
diese Weise brauchen wir nicht über die Zahl von Argumenten und so nachdenken.
Solange die Generatoren Werte liefern, wird gerechnet.
Und wir können verschiedene Generatoren parallel laufen lassen und ihre Werte
dann in Transformatoren verknüpfen
Die transformieren die gegebenen Werte in irgendetwas anderes. Dann haben
wir die Kombinatoren, die nehmen mehrere Ströme und kombinieren das
aus den verschiedenen Strömen kommt und erzeugen was Neues raus.
Dann haben wir, Moment, noch Seelektoren und Konstrukturen,
um eben damit Ströme zu bauen.
Ja, mal ein ganz simples Beispiel. Ich habe hier,
sagen wir mal, einen Strom von weiblichen Wesen,
einen Strom von männlichen Wesen, dann haben wir Transformatoren und Kombinatoren,
die machen aus diesen Strom von Singles,
machen Sie einen Strom von Pärchen.
Und dann können wir daraus aus dem Pärchen per Transformatoren dann noch Nachwuchs erzeugen.
Gut, also so können wir uns unseren Strom ganz gut vorstellen.
Und es gibt Programmiersprachen, grafikorientierte Programmiersprachen, die es uns erlauben.
Einfach Generatoren zu nehmen, Transformatoren und das Verbinden.
Also was ich richtig liebe und was Spaß bringt,
ist in den Entwicklerwerkzeugen auf dem Mac gibt es den schönen Quotes-Composer,
mit dem kann man Bildverarbeitung elegant machen oder Computergrafik
Einfach Ströme von Bildern und sie verknüpfen und anzeigen.
Damit kann man sich Bildschirmschoner und alles Mögliche bauen. Ja,
also einfach zwischen statt Funktionen aufzurufen, machen wir mit der Maus
einen Dreck in Drop und verbinden die Ausgabe vom einen,
mit der Eingabe vom anderen ist, was automatisch die sonntags,
der Typ geprüft.
Also alles quick and easy. Ja, ein Strom,
den wir haben, der ist zunächst mal wie eine Liste in eine rekursive Struktur.
Wir haben einen endlichen Kopf und dann haben wir einen
potenziell unendlichen Reststrom oder sie sind leer.
Sieht genauso aus wie die Definition einer Liste
Wir können sie unendlich lang definieren, weil wir immer nur ein Element zurzeit verarbeiten.
Und deshalb sehen die Funktionen dann so aus,
dass sie immer den Kopf eines Stroms verarbeiten und den Rest in der Bearbeitung zurückstellen,
typisch Lesi, und sagen, wenn jemand für den Rest auch wissen will,
wie es geht, dann machen wir es,
aber es muss einem gefragt werden.
Vorher passiert nichts. Das ist ein Versprechen. Wenn wir
jetzt Funktionen schreiben, müssen wir die Funktionen aber so schreiben,
dass sie in diesem Programmierstil reinpassen
Was man vergessen kann und sich in die Haare reiben,
wenn man in der Sprache Lesy unterwegs ist, das sind endrekursive Funktionen.
Also, dass man Sachen im Akkumulator zusammenbaut und
erst sind die von erst am Ende das Ergebnis zeigt,
in denen Akku dann rausholt, das bringt das Strommodell zum Absturz.
Dann können wir es nicht mehr wagen,
den Strom der natürlichen Zahlen als Argument zu übergeben.
Also, der Stromorientierten Programmierung sind wir gezwungenermaßen dazu verpflichtet,
dass wir Stromfunktionen in Kopfform schreiben und die sind eben in
der Regel nicht entrekorsiv, weil sie keinen Akkumulator haben können.
Eine Stromfunktion ist eine Funktion, die den Eingabestrom auf den Oscarbestrom abbildet.
Und dabei werden die Eingabewerte elementweise verarbeitet und direkt wieder in Oscarbewerte abgebildet,
ohne dass man den Rest des Eingabestroms sehen muss.
Wenn das nicht geht, dann geht es eben nicht mit einer Lesewelt an.
Und Kopfform, das ist eben das, was man braucht dafür.
Eine Funktion ist im Kopfform definiert, wenn das Kopfstück des Ausgabestroms
nur vom Kopfstück des Eingabestroms abhängt. Also wenn wir Funktion so geschrieben haben,
dass sich immer nur den Kader der Liste sehen muss,
Und dann kann ich eine wunderbare Sprungfunktion machen,
weil ich den Rest dann zurückstellen kann.
Wenn wir dagegen Akkumulator haben, wenn wir es eine,
einfach mal die Map-Funktion, die eine Liste nimmt und sie auf eine andere abbildet, beschrieben.
Aber mit einem Akku. Typischerweise in Reckett würden wir
davon ausgehen, die Liste ist immer endlich.
Und dann machen wir es in entrekursiver Form, um eine echte Trikots.
Iteration zu haben, indem wir, wenn die Liste leer ist,
den Akku zurückgeben und in allen anderen Fällen sagen wir rekursiv
Dass für die Funktionen anwenden, auf den Rest der Liste.
Und dass Cons machen wir im Akku. Das haben wir
ja geübt, geübt, geübt, bis zum Gehtnichtmehr.
Sollten sie in der Klausur alle können. Und diese Akku-Variante,
die ist aber nur wirklich nicht in Kopfform.
Weil wir kriegen hier erst was zurück, wenn der ganze Akku zusammengebaut ist.
Also das passt nicht in die Lesewelt, jetzt passte in
die Strom orientierte Programmierung. Die allgemeine linearere Rekursion,
bei der wir einfach die Funktion aufs erste Element anwenden und
dann rekursiv die Restliste abbilden,
die ist in Kopfform,
ideal für Ströme
Genau. Wir können stromorientierte Programmierung übrigens in Recket einbauen,
ohne dass wir da auf Makros zurückgreifen müssen.
Ja. Und dafür gibt es zwei Sprachelemente, die uns die verzögerte Ausfährtin bringen.
Also die sind einfach in der Sprache schon drin.
Deshalb brauchen wir den Kram nicht, wir brauchen wir
keine eigenen Makros mehr, um die Lesie Evaluation hinzukriegen.
Die beiden Elemente heißen Gelay und Force. Ja. Wenn wir
die Lesagen für eine Datenstruktur, dann wird sie verzögert und zurückgestellt.
Und mit Fonds erzwingen wir die Auswertung und er wird so lange ausgewertet,
bis man wieder auf ein Delet stößt und dann wird der Rest wieder zurückgestellt.
Und mit diesen beiden Elementen können wir in der,
eigentlich in jeder lispindischen Programmiersprache, Spröme implementieren.
So. Also der Strom ist wie die Liste linear rekursive Datenstruktur,
die wollen wir jetzt als Datentyp implementieren.
Die kann leer sein oder aus einem Kopf bestehen oder aus
einem Restelement. Und der ist wieder vom Typ Strom
Von diesem Rest, dem Schwanz hier, wie es bei
den Strömen heißt, Tele. Da ist zu jedem Zeitpunkt
nur endlich weit ist ausgewertet und der Rest ist das Versprechen.
Und da gehen wir jetzt mal weiter. Emdistream?
Ja. Das wäre eine Basisfunktion, die wir brauchen.
Wir wollen für einen Strom wissen, ist es der leere Strom?
Wir machen, brauchen einen Cons, das für ein Element davor setzen können.
Wir müssen einen Akzessor haben, der das Kopfelement nimmt,
einen Akzessor, der uns den restlichen Strom gibt.
Und dann wollen wir im Strom vielleicht noch das Füte-Element haben
Stream-Ref. Also all diese Basisfunktionen finden Sie, wie gewohnt,
auch in der SE3-Bib und können damit mal das ausprobieren.
Jetzt schauen wir uns mal an, wie wir das hinkriegen können.
Diese Conses und so weiter. Wir wollen jetzt ein Constream
machen und das führen wir als ein Makro ein.
In dem Moment müssen wir wirklich tatsächlich wieder auf die Makros zurückgreifen,
weil wenn wir Hier auf Argumentposition etwa stehen haben und das ist eine Funktion,
wird sie sofort ausgewertet und das wollen wir nicht.
Also sagen wir, für Constream machen wir es einfach so,
dass wir Cons verwenden.
Das erste Element soll vor den Ausdruck gesetzt werden,
damit der B übergeben wurde, aber für B machen wir ein Delet,
das süllen wir ein ins Delet.
Also hier schreiben wir den Aufruf von Constream so,
dass wir das Delet schon freiwillig anbieten und
die Benutzer es nicht machen müssen
Hätte, braucht keinen Verzögern, da wollen wir
das erste Element haben und das sollte immer zugreifbar sein.
Wenn wir jetzt auf den Rest zugreifen wollen,
dann müssen wir diese Delace jetzt auflösen und Dreckett sagen,
du tu mal bitte was. Auf das Tailstream guckt,
ist der Strom leer, kriegen wir einen leeren Strom zurück.
Wenn der Rest leer ist, kriegen wir auch einen
leeren Strom zurück. Wenn es ein Paar ist,
dann bringen wir den Rest zurück und in allen anderen Fällen
wird ein Forst gemacht,
um das auszuwerten und dann kriegen wir
den Wert zurück
Okay. Wir brauchen noch einen Test, ob ein Strom leer ist.
Da brauchen wir keinen Leder, fragen wir einfach nur,
ist die Liste, als sie was implementiert haben, leer?
Und der Ämte, als definierte Konstante,
einen leeren Strom, ist einfach die leere Liste.
Jetzt gucken wir uns mal den Strom der natürlichen Zahlen ab
einer Zahl an. Wir hatten da schon bei Lezy besprochen,
wie wir es mit Lezy machen können.
Hier machen wir es ja jetzt mal mit unserer eigenen Stromorientierten
Variante selbst eingebaut. Also die natürlichen Zahlen ab N.
Dann nehmen wir als erstes
die Zahl
Und dann sagen wir, jetzt müssen die Zahlen kommen, die danach kommen.
Aber wir sind Lesy. Und sagen einfach Delay,
tun dahin einen Aufruf für in Teachers-Strum-End, aber wir beginnen jetzt bei Endplus 1.
Also wenn jetzt jemand wirklich die zweite Zahl in dem Strom
haben will, dann muss dies Gelee hier aufgelöst werden.
Und diese Rekursion gemacht werden. Aber normalerweise geschieht es dir nicht. Das heißt also,
wir sehen hier die Implementation für eine Rekursion ohne Abbruchbedingung,
statt eine Abbruchbedingung vorzusehen und zurückzuhandeln zu dem Punkt,
wo der Abbruch greift, machen wir eine Rekursion,
beginnt von der kleinen natürlichen Zahlen gännen unendlich.
Aber wir sagen, bei jedem Schritt verzögern, abwarten,
ob das wirklich jemand haben will. Und wenn wir jetzt das aufrufen,
wir wollen den Strom der natürlichen Zahlen ab drei haben,
dann gucken wir uns das an,
dann steht da, ja, das ist die Liste,
die beginnt mit drei und hierhinter steht Struct Promise.
Also wenn wir das wirklich haben wollen,
müssen wir eben darauf zugreifen und erzwingen, hier,
das mit einem Force, dass es ausgewertet wird.
Ja, ganz typisch eben für diesen,
für Ströme ist das die Rekursion über die Nachfolger
geht und nicht über den Vorgänger
Also in allen Funktionen, die wir bisher geschrieben haben,
haben wir die Rekursion über den Vorgänger gemacht und uns gegen die Null.
Also eine in endliche Entfernung liegende Abbruchbedingung gehangelt. Jetzt schreiben wir
es einfach hin. Wie geht es in Zukunft weiter?
Wir brauchen uns nicht um die Abbruchbedingungen zu kümmern.
Und das gucken wir uns jetzt mal in einem Beispiel an,
für etwas, was wir im Schulunterricht geübt haben.
Siebtes Ära Tostin ist. Wir wollen alle Brimzahlen haben
und wollen jetzt den Strom der Primzahlen schreiben,
von dem wir wissen, dass er unendlich ist.
Das ist
ja inzwischen bewiesen worden
Also schreiben wir es auch so, dass wir auf diese Weise,
alle unendlich vielen bekommen können, wenn wir eine lange genug warten.
Das Sieb geht ja so, dass wir zunächst mal den Strom der natürlichen Zahlen nehmen und es filtern,
indem wir die erste Zahl nehmen und sagen, das ist eine Primzahl,
dann wird es die Eins als Primzahl erwischt. So,
dann gucken wir uns diese Zahl an, wir wissen,
es ist eine Primzahl und wir wissen alle Vielfachen dieser Prim-Zahlen sind keine Primzahlen,
also sprechen wir jetzt die Vielfachen aus, die Vielfachen von zwei,
Das erste Element hier ist eine Primzahl,
davon nehmen wir die vielfach an, das kriegen wir die vier weggestrichen.
Die sechs und so weiter. Und wenn wir alle vielfachen ausgesprochen haben,
was ein bisschen dauern kann, weil wir unendlich viele natürliche Zeilen haben,
dann nehmen wir die erste Zahl, die dann übrig bleibt,
nachdem wir die zwei weggenommen haben,
die drei und streichen die vielfachen von drei aus.
Und die nächste Zahl, die kommt, ist die fünf.
Jetzt streichen wir die Vielfalten von fünf aus. Ja,
nur fässt man sich nur am Kopf und fragt,
wie erreiche ich eigentlich den Moment,
dass ich zu den Vielfachen von drei komme,
weil ich ja schon ziemlich viel zu tun habe,
die Vielfachen von zwei auszustreichen.
Das sind unendlich viele
Und da helfen eben die Ströme. Wir beschreiben einfach nur
den potenziellen und endlichen Strom der Vielfachen von zwei und der wird ausgestrichen.
Aber Lesi rechnet für uns immer nur die erste Zahl.
Solange bis irgendjemand es braucht. Also wir können hier wirklich sagen,
filtern mit den unendlich vielen Vielfachen von zwei,
was dann übrig bleibt, ist die drei,
nehmen die Filter mit den Vielfachen von drei und so weiter.
Und die kriegen wir. Also schauen wir uns das Schema
nochmal an. Die Unendliche Menge der natürlichen Zahlen,
Folge der natürlichen Zahlen außer Eins bildet ein Strom von Kandidaten
für Primzahlen
Der Kopf des aktuellen Kandidatenstroms ist eine Primzahl. Das wäre
dann die zwei. Und mit der legen wir los.
Wir nehmen den Kopf des Kandidatenstroms und sieben den Reststrom,
in dem wir alle Vielfachen mit Filter rausnehmen.
Dann nehmen wir den gesiebten Reststrom als neuen Kandidatenstrom,
gucken wir uns das erste Element an.
Und das ist jetzt die drei. Sieben wieder mit der.
Und es entsteht ein Strom von Primzahlen aus den Köpfen der Kandidatenströme.
Also weil alle Teilfunktionen in Kopfform sind und wir deren Köpfe
immer kriegen können, kriegen wir die gesiebten Zahlen raus ohne jebes
unendlich gerechnet zu haben
Also die, zuerst haben wir nur die zwei,
dann nehmen wir die drei weg, haben dies hier,
haben jetzt den Fünf als Kopf, nehmen die weg,
haben hier unsere Liste von Frieden zahlen, die wächst und wächst,
haben hier jetzt die sieben und so weiter.
Okay, wie schreiben wir das? Sieb,
indem wir Filter streamen verwenden, Filter streamen,
ist eine der Stützfunktionen, genauso wie wir Filter auf den Listen haben,
haben wir eben auch einen Filter für Strömen.
Okay, also Stützfunktionen nicht teilbar X durch Y,
wenn es nicht gilt, dass Null der Divisionsrest von X gegen Y ist.
Und damit können wir die Teilbarkeit testen
Ja, wir rufen die Funktionen, Filter auf,
indem wir einen Curry machen. Von der Funktion,
die auf Teilbarkeit testet, mit dem Kopf von dem Strom.
Und sagen, dann rekursiv, siebe, den Restsprung.
Ja. Und dann, wenn wir jetzt sagen,
definiere, die Primzeilen sind siebe, die Integer-Zahlen ab zwei,
dann geht das Ruckizucki.
Und wir haben die Liste aller möglichen Primzeilen.
Gerade eine Sekunde Reaktionszeit oder so. Bloß Durch die
verzögerte Auswertung ist ja noch überhaupt nicht viel gerechnet worden.
Es sind nur die ganzen Köpfe der Ströme erzeugt worden.
Wenn wir jetzt wirklich Primzahlen sehen wollen, dann nehmen wir
die Funktion Take, die den Anfang einer Liste nimmt.
Ja, das Anfangselement einer Liste. Und wir sagen hier jetzt mal Take-Stream,
10. Und dann kriegen wir tatsächlich die ersten 10 Primzahlen.
Wenn wir 100 sagen würden, kriegen wir die ersten 100.
Aber wir müssen bei Take-Stream immer eine endliche Zahl sagen.
Und dann wird endlich gerechnet.
Alle Listen sind automatisch Ströme,
ja, wenn wir in die Sprache Lesereckett reingehen,
wir können das, was wir eben geschrieben haben,
in Lesiereckett einfach mit Filter so schreiben, ja,
Tek und Rob verhält sich alles wie Ströme und wir können in Lesiereckett das Siebtes ihrer Testines genauso schreiben.
Und wenn wir jetzt mal an die Sprache Lesie reinschalten,
können wir so definieren, die natürlichen Zahlen ab N.
Da müssen wir einfach nur sagen, Konz N,
vor die natürlichen Zahlen N plus 1,
Damit wir jetzt aber auch die Liste wirklich zu sehen kriegen,
müssen wir erzwingen, dass für alle Argumente von dem Teekir es wirklich gerechnet wird.
Also Beng Beng, ja, also das Foss,
es gibt dem einen auf dem Schädel und dann kriegen wir tatsächlich die Liste angezeigt,
was denn jetzt die ersten zehn natürlichen Zahlen sind.
Und in der Sprache lese ich unser Sieb geschrieben,
da würden wir jetzt einfach sagen, definiere das Sieb über den Stromkons,
von ersten Element des Stroms mit einem Filtern Dass wir auf Nicht-Teilbarkeit testen,
durch diese Zahl hier, ja.
Und das erste Element des Stromes. So, und dann machen wir,
wenn wir das Sieb auf den Reststrom an. Ja,
und dann würden wir jetzt sagen, die Primzahlen,
alle denkbaren Primzahlen sind Siebel, die natürlichen Zahlen ab zwei.
Genauso wie wir es eben in unseren eigenen handgemachten Stromfunktionen gemacht haben.
Okay. Vielleicht ist es auf einen anderen Fall angewendet. Wir hatten,
als wir mit Listen begonnen haben, uns den Spaß gemacht,
einen Sprachgenerator zu schreiben, der einfach ein Wörter nimmt,
einen Satz für ein Grundwort,
schätzt ein paar grammatikalische Regeln und dann munter Sätze in dieser Sprache aus sich raussprudelt.
Und da hatten wir gesehen,
dass wir ein Endlosschleifen kommen können, wenn wir zulassen,
dass der wiederholte Elemente durch Rekursionen ausgedrückt sind.
In der Sprache Lesi können wir das ohne Abbruchbedingungen machen,
indem wir da tatsächlich jetzt einen Generator schreiben,
der die Sätze der Sprache erzeugt und damit Lesi eben immer wartet,
bis man es wirklich erfahren will
Also dieses Generate All, jetzt in der Sprache, Lesi geschrieben,
da können wir über Ströme das Ganze zusammensetzen und uns dann in dieser Sprache das erzeugen lassen.
Steht im Skript drin, ich habe die Folien jetzt aber erstmal übergangen,
damit wir noch zu einem anderen Thema kommen,
nämlich das, was ich gern noch anzeigen wollte,
damit wir uns nochmal überlegen, wann kommt eigentlich der Moment,
wo wir doch mal zur imperativen Sprachelementen greifen?
Also wenn man überhaupt die ganze Zeit nur imperativ programmiert hat,
kommt einem das ziemlich natürlich vor,
dass man es imperativ macht
Hat eigentlich gar keinen anderen Wunsch. Aber wenn man jetzt in einer schönen,
funktionalen Sprache ist und dann vor der Gewissensfrage steht,
wann werde ich imperativ und verletze die Bezugstransparenz.
Da gibt es eben ein paar Momente, wo man es doch tun muss.
Und es gibt ein klassischer Entwurfsmuster, wie man diese imperativen
Stellen so verbirgt, dass sie einen weiter nicht stören.
Dass der Rest des Programms weiterhin rein funktional aussieht.
Und deshalb als eine der wichtigen Anwendungen,
wo wir uns überlegen, wie verwende ich Imperatives,
geschickt und elegant mal
Cashing und Memoisation
Also, sie wissen ja, man kann Mehrfachberechnungen vermeiden,
indem man, was man einmal ausgerechnet hat,
einfach in einen Cash tut und es aufhebt,
für den Fall, das nochmal gebraucht wird.
Also, das ist Werte, die man schon hat,
hebt man auf in der Hoffnung,
dass es mal wieder gebraucht wird.
Und wenn es dann kommt, greift man darauf zu.
Und sie kennen das wahrscheinlich von ihren Webbrowsern,
dass die ihren Speicher vollmüllen,
indem sie so doch gelegentlich ab und zu mal wieder
Cashes lernen müssen,
um ihren Speicherplatz zurückzukriegen
Weil man kann es eben auch planvoll für andere Sachen machen.
Zum Beispiel Funktionen, die aufwendige Berechnungen haben.
Warum sollen die sie nicht auch merken,
was sie schon einmal berechnet haben und nicht jedes Mal von vorne anfangen,
wenn man die Funktion wieder aufruft mit dem gleichen Wert.
Und Memoisation, das ist jetzt eine Technik,
mit der wir eine bestehende Funktion nehmen. Eine,
die verschwenderisch jedes Mal immer wieder alles neu ausrechnet und sich nicht gemerkt hat,
was sie schon mal gemacht hat. Und sie so transformiert,
dass diese Funktion sich jedes Mal, wenn sie einen Wert hat,
in der Tabelle einträgt und sagt, den hatte ich schon,
den hatte ich schon, den hatte ich schon.
Und dann brauche ich hinterher Nicht selbst beim Programmieren.
Meine Funktionen alle so schreiben, dass sie es merken.
Ich sage einfach der Memo-Funktion, wenn wir Funktionen rein.
Und ab jetzt wird diese Funktion immer und ewig sich merken,
was sie schon mal ausgerechnet hat.
Und das können wir uns mal mit dem Schreckbeispiel der Baumrekursion anschauen.
Wir hatten uns hier wirklich als abschreckendes Beispiel für einen hohen Aufwand,
die Piponachi-Zahlen angeschaut. Wenn man die einfach so schreibt,
wie sie definiert sind, mathematisch, dann haben wir Hier Mehrfachaufrufe.
Also wenn wir die Entefibunati-Zahl haben wollen,
ist es die Summe aus der N-minus-ersten und der N-minus-zweiten.
Und die N-minus-erstefibunati-Zahl, die hat, um sie sich zu berechnen,
berechnet sie die N-minus-zweite eigentlich schon. Und die davor auch,
das heißt, wir haben viele, viele Mehrfachberechnungen.
Nochmal diese Folie, an der wir das gesehen hatten,
schon die fünfte Fibunati-Zahl, berechnet zweimal die dritte.
Dreimal die zweite, eins, zwei, drei,
vier, fünfmal die erste.
Und dreimal die nulte. Also eine endloses Chaos,
ein Mehrfachrechnung
Bei der ersten Lösung, die Funktion elegant zu machen,
haben wir sie einfach umformuliert in eine Variante mit Akkumulator.
Die dann auch noch entrekursiv war. Aber wir können das vielleicht,
ohne dass wir Gehirnschmalz reinstecken müssen, mit Memo-Funktionen machen.
Wir wollen jetzt eine Funktion schreiben,
als Werkzeug, an die können wir andere Funktionen übergeben.
Und dann wird der Code dieser Funktion transformiert.
Wir machen zum ersten Mal eine Code-Transformation.
Es kommt ein neuer Funktionscode zurück, eine neue Funktion.
Und diese Funktion ist gescheiter, die rechnet nicht zweimal
Und was macht sie? Sie führt eine Tabelle für
schon berechnete Funktionswerte, ja. Und dann prüft sie,
ob das Argument schon als Funktionswert verfügbar ist, ja,
dann gibt sie den Funktionswert als Resultat zurück, wenn sie den schon hat.
Und in allen anderen Fällen berechnet sie ihn einmal, speichert ihn aber in der Tabelle.
Und dann müssen wir noch den Namen dieser neuen Funktion,
die ein bisschen pfiffiger vorgeht,
jetzt an den Namen der alten Funktion binden,
die wir schon hatten,
damit die alte Funktion sich jetzt clever verhält.
So,
was muss also geschehen
Der Kern besteht darin, dass wir erstmal eine Tabelle brauchen,
was immer wir dann für Datenstruktur nehmen. Ich empfehle dafür,
dass meine Hash-Tabelle nimmt. Und dann brauchen wir eine Funktion Store,
die würde also in diesem Hash jetzt irgendetwas tut,
den Wert ablehnen, eine Funktion Ritrief,
die aus der Tabelle rausholt, was wir da abgelegt haben.
Eine Funktion McTable müssen wir noch schreiben, die ihn tatsächlich erzeugt.
Ja, und der Rumpf der neuen Funktion,
die ist in Schurwald, der guckt nach, aha,
für welchen Wert soll ich meine Funktion jetzt aufrufen
Und dann guckt sie als erstes nach. Gibt es
den Wert schon? Das ist dort, weil,
also mit Retrieve, greifen wir auf die Tabelle zu.
Wenn es ihn gibt, dann geben wir den zurück.
Andernfalls speichern wir den Aufruf der Funktion FN mit dem Argument X
in der Tabelle. Und in Schuhewall ist also die Funktion,
die zurückkommt und die stattdessen immer aufgerufen werden soll anstelle der Funktion F,
die wir ursprünglich hatten.
So, das hüllen wir jetzt in eine Funktion Memo
ein, die unsere Funktionen transformiert.
Definiere die Freien. Nimm mal für irgendeine Funktion
Für eine Funktion höhere Ordnung. Wir übergeben eine Funktion.
Zurückkommt die neue bessere Funktion. Hier kommt der Lat-Track-Kern,
wo wir eine Tabelle haben.
Ich habe hier mal als eine Baby-Implementation einfach eine Assoziationstabelle genommen.
Das ist natürlich nicht sonderlich effizient, sondern wie gesagt,
Record hat die Datenstruktur Hash-Tabelle und schreiben sie da stattdessen.
Milk Cash und Gertesch rein und sie haben eine wirklich effiziente Variante.
Also was machen wir hier? Unsere Tabelle ist zu Anfang
einfach nur eine Liste und Um einen Wert zu speichern,
machen wir Set Bang.
Hier haben wir jetzt wirklich den imperativen Moment.
Die Tabelle muss verändert werden. Ein Kunst von Arc und Wall
. Und es wird vorne eingehängt, haben die Assoziationslüfte.
Und das Retrieve sucht hier mit Assoc. Das ist also
die billigste Implementation. Wie gesagt, nehmen Sie,
wenn Sie es ernsthaft brauchen, Millcash und Geptesh.
So, und jetzt nehmen wir unsere Fibonati-Zahlen.
Ja. Übergeben die Funktionen Fip an Memo.
Und haben eine neue Funktion Memo-Fip. Und wenn wir die
für drei aufrufen,
kriegen wir
die drei zurück
Der musste jetzt leider wirklich immer rechnen. Er hat gerechnet,
Fip von 3, Fip von 2, Fip von 1,
Fip von 0. Und jetzt hat er hier schon wieder Fip von 2 aufgerufen.
Moment, er hat doch hier vorne ihr Fip von 1
aufgerufen, das hat er eigentlich schon gehabt.
Das ist gemein, ne? Was wir hier sehen,
wir haben einen kleinen Punkt übersehen. In der Rekursion,
da ruft hier dieses, diese Funktion,
die den Wert speichert, Moment, hier in Schuhe,
weil,
ja?
In der Rekursion
Ruft sie dummerweise noch unsere alte Fip-Funktion auf, die nicht optimiert ist.
Da müssen wir noch was gegentun. Aber ansonsten hat das
schon ganz gut geklappt, wenn wir nämlich jetzt,
wir haben nur Fip für drei nochmal aufrufen, ein drei weißes,
das hatte ich schon, nee, das kriegen wir.
So, jetzt werden wir wirklich brutal und nehmen sozusagen den doppelten Hammer.
Jetzt fragen wir einfach in der Tabelle den definierten Wert für Phipp
ein und ändern Phipp zu dem, was Memmel zurückgegeben hat.
Und wenn wir das machen, dann wird jetzt im rekursiven Aufruf
die rekursiven Aufrufe gehen sofort auch an das FIP,
was wir haben, was schon den Wert gespeichert hat.
Wir haben jetzt für den Aufruf von Memmel FIP von 4,
haben wir gesehen, es ist eine reine linde Arre Iteration geworden.
Und nachdem es bei Null angekommen ist, kommt es zurück.
Wenn wir jetzt für eine neue Zahl aufrufen,
ach, hatten wir noch nicht, dann sehen wir,
wir haben tatsächlich einen linde Jahren Aufwand,
ergebnis zur Acht und ab dann kennt er die Werte
alle schon
Wenn ich jetzt, die Fibonachi-Zahlen,
also als ich zum ersten Mal gerechnet hatte,
dass es ungefähr zehn Jahre her,
da hatte ich noch eine Spark 4 gefunden, dass da Aufrufe,
ohne Memo, mit einem End von fünf, ja gut,
da hat mir noch keine Laufzeit gesehen, bei zehn,
auch noch nicht, bei 20 wird es merklich,
bei 30 sind wir tatsächlich schon,
bei 730 Millisekunden,
bei 40 fängt es an weh zu tun und bei 100
wird man ungeduldig
Da hatten wir, jetzt hatte ich es ohne Mimma-Funktionen gar
nicht mehr rechnen können, da habe ich irgendwann abgebrochen.
Aber in der Mimmo-Variante sind es gerade 20 Millisekunden gewesen.
Das heißt, die Mimmo-Technik nimmt es uns ab, die Sprecherverwaltung des Cashing selbst zu machen.
Wenn wir eine Funktion haben, für die es sinnvoll ist,
dann können wir für diese Funktion einfach die Funktionen nehmen,
sie transformieren lassen und wir haben eine neue Funktion, die besser zu verwenden ist.
Und selbst für 1.210 Millisekunden, Wie gesagt, Normalbaumrekursive Variante,
die war schon mit einem Argument von 100 nicht mehr so schön.
Was haben wir jetzt gebraucht in imperativen Elementen, um das hinzukriegen?
Also um eine Funktion so zu transformieren,
dass sie immer noch funktional aussieht, aber in Wahrheit imperativ ist,
haben wir zweimal zum Imperativen Holzhammer gegriffen,
einmal für Speichern an der Tabelle, nicht die Store-Funktion,
die macht ein Set und die Modifikation auf die Referenz,
wo wir den Namen in der Symboltabelle geändert haben.
Damit haben wir jetzt nochmal einen kleinen Ausflug in die Imperative
Programmierung gemacht und verschiedene Verarbeitungsmodelle gesehen.
Also woran sie in Zukunft jetzt denken sollten, wenn sie im vierten,
fünften, sechsten Semester sich wieder mit anderen Programmierstilen beschaffen.
Es gibt keinen besten Programmierstil. Ja, ich habe es
in der allerersten Vorlesung gesagt und ich wiederhole es nochmal.
Es gibt einfach Programmierstile, die perfekt zu gegebenen Problemen passen.
Und ihre Aufgabe als Informatikerin oder Informatiker ist,
sich immer zu überlegen, hier ist meine Problemwelt,
Wie modelliere ich das? Welches ist die Programmiersprache,
die die mächtigsten Werkzeuge hat und am treffendsten das Problem so zu beschreiben,
damit ich einen verständlichen Code bekomme, der effizient ausgeführt werden kann?
Das ist die eine Seite. Die andere Seite ist,
man kapariziert sich irgendwann im Leben drauf,
dass man eine universelle Programmiersprache lernt, die berechnungsuniversell ist.
Und die sehr ausdrucksmächtig ist. Und meistens ist sie für
die Welt ein Problem, mit dem man zu tun hat,
gut geeignet und bietet einem oft Schlupflöcher in andere Programmiersprachen
Habe mich darauf eingeschossen, ich versuche möglichst alles im funktionalen Programmiersprachen hinzukriegen und wähle eine,
die den Komfort des Schlüpflochs in die imperative Programmierung bietet und einem diese ganzen Sprachelemente auch ermöglicht.
Und damit komme ich ziemlich gut zurecht. Manchmal fange ich
an zu wünschen und zu träumen und sage, Mensch,
also jetzt habe ich eigentlich ein Problem über Relation.
Und mit meiner Ausbildung als Informatikerin tut es mir in der Seele weh,
Probleme, die man relational gut löst, funktional schreiben zu müssen.
Na gut, dann stöpsel ich ein Bracket Pro-Lockereien oder ein Kommenlist zu Pro-Lock,
um die Teile dann relational beschreiben zu können.
Was ich mir abgewöhnt habe, ist, Vene ist allzu häufig,
ein Paket zu schreiben, das viele Programmiersprachen separat verwendet,
weil einem jedes Mal das um die Ohren fliegt,
wenn Systemerweiterungen kommen,
wo man dann in drei Programmiersprachen die Kompalle aktualisieren muss und so weiter.
Also was ich gerade noch hinnehme, ist,
dass man den Ausflug in andere Programmiersprachen Im selben Paket macht,
wenn man Hardware anschließen muss.
Da braucht man nun mal die Gerätetreiber. Und um das effizient
zu machen. Gut, dann greift man an der Stelle
über ein Found-Function-Interface auf die Treiber dafür zu, drauf.
In Reckett finden Sie übers Vor- und Function-Interface angeschlossen.
Zum das ganze Grafikpaket OpenGL, um wunderschöne Echtzeitgrafik zu machen.
Im Praktikum, wenn du zu uns ins Programmierpraktikum kommen,
können sie. Das Bildverarbeitungspaket wird Reckett kennenlernen.
Oder auch,
wenn sie mir
Im Bild, wir machen im Bildverarbeitungspraktikum im Moment vor allem mehr Personenspiele,
verteilt auf Rechnern und da wird eben über Kameras festgestellt,
was ist der Spielstand und damit kann man das gut machen.
Was wir jetzt vorbereitet haben,
für künftige Praktika und das auch schon gut funktioniert,
ist, dass sie eben auch üben können mit der Connect,
mit Vicky Nect, kann man inzwischen eben auch gästikulieren,
statt andere Dinge machen.
Also auch, wir könnten also auch scherisch dein Papier,
das im Praktikum spielen mit der Kinect dann.
Also solche Sachen sind möglich und das gilt eben dann übers Fördern,
Function-Interfaces, wo man dann doch mal tatsächlich in eine andere Programmierersprache rein muss,
weil Hardware effizient bedient werden muss.
Da kommt man nicht dran vorbei. Aber zu viele verschiedene Programmiersprachen
zu verbinden, ist meistens schwierig. Es ist tatsächlich einfacher,
dass man sich überlegt, wie erweitere ich die Sprache,
die ich gerade habe, um den anderen Programmierstil einzubetten.
Und das habe ich nirgendwo so schön gesehen wie in der Liftwelt,
dass man, wenn eine neue Programmiersprache kommt,
die neue Programmiersprache
Stile treffen beschreibt und das sowas für eine Roppe der Steuersprache oder so,
dass man das sehr gut einbinden kann,
eben mit einem defineden Syntax und sich seine eigene Programmiersprache überlebt.
Das ist also das, was dann eigentlich auch Spaß bringt,
sich für eine Problem angemessene Sprache auszudenken, in der man sehr genau beschreibt,
es ist mein Sprachelement und das wird implementiert.
Okay, was machen wir jetzt das nächste Mal noch?
Wir haben jetzt ja noch genau einen Termin.
Ich möchte in beim nächsten Mal gerne noch ein Beispiel zeigen,
aus der Uhrzeit, wo funktionales Programmieren erfunden wurde.
Das ist der General Program Server, den hat man ein bisschen zurückgestellt.
Und dann werde ich den letzten Termin zum Abschluss nehmen,
um das Semester nochmal Revue passieren zu lassen,
sodass wir zum einen durchgehen, die einzelnen Programmierspiele,
ihre Merkmale vergleichen.
Sie haben im Skript eine Liste von vielen Fragen zur Klausurvorbereitung,
auf die werden wir auch nochmal reinschauen und vielleicht nochmal
einen Blick in eine Probeklausur werfen,
wenn dann dafür noch
Zeit ist
Ich gucke jetzt noch mal, wo hätten wir den General
Problem-Solver uns darauf vorzubereiten, was wir dort besprechen werden.
Es war ja in dem Kapitel über Fallstudien,
ausgewählte funktionale Algorithmen. Und das stammt aus der Zeit,
als die Mehrheit, der Programmierer wirklich große Projekte entweder
ein Vordrang
gelöst hat
Sambler-Code oder Sprachen wie Algul und so weiter im Raum standen.
Und die ganze kommerzielle Datenverarbeitung lief in Kobol. An der
Universität hat man sich damals trotzdem schon mit Liz befreundet,
aber die Urform von General Problem Säuber war auch noch nicht in Liz realisiert,
sondern eben notdürftig in der anderen Programmiersprache.
Peter Norweg hat die zündenden Ideen aus dem General Problem Säuber
neu geschrieben und sie entkommen ist realisiert.
Und das habe ich als Vorbild genommen, um ihnen das in Recket zu zeigen.
Also was ist die Idee? Es stammt aus der Zeit,
als man darüber nachgedacht hat, das aufzugreifen,
was von Leibnitz eigentlich schon eingebracht wurde,
wenn man ein logisches Problem hat, man löst es durch Nachdenken.
Mich von Leibnitz ist aus der Zeit, als die ersten Kalküle eingeführt worden,
der Spruch überliefert, lasst es uns rechnen.
Wenn man eine Frage hat, wo gefragt wird,
ja ist das wahr, ist es falsch,
ist es wahr, ist es falsch, hat er gesagt,
wir setzen uns hin mit Zettel und Papier,
verknüpfen alle Aussagen logisch und am Ende sagen wir ja oder nein und beantworten es.
Bleibt mir zwar eine lange, lange,
bevor die ersten Computer erfunden wurden,
aber als Babbage seine Analytical Engine gebaut hat und Eda Lovelis als erste Person in der Welt hingegangen ist,
dafür Programme zu schreiben,
da ist sie wieder auf die Ideen von Leibnitz gestoßen und hat gesagt,
ja, und was wird es sein?
Wenn ich diese Maschine jetzt so programmiere, dass ich das Denken programmiere.
Sie hat damals schon gesagt, mit einem Gerät der Mächtigkeit,
der Analytical Engine, können wir Denkprozesse nachbilden?
Das ist lange auf der Strecke geblieben,
die Analytical Engine hatte so viele Hardwareprobleme,
dass selbst das so für die Forschungsgelder eigentlich gedacht waren,
Navigationskarten zu rechnen und so nicht so gut geklappt hat.
Das waren die ersten Rüstungsgelder, die in den Sand gesetzt
wurden, von der Marine. Aber die Ideen,
die man damals sind, geblieben und wurden wieder aufgegriffen,
als die Computer ein bisschen mächtiger wurden.
Und da hat man eben versucht, dahin zu kommen,
davon wegzukommen, das zu machen,
was alle gemacht haben zu dem Zeitpunkt, gegeben ein Problem.
Wir versuchen rauszukriegen, bis zu lösen ist,
dann schreiben wir einen Algorithmus und erlöst das Problem
Und der Algorithmus muss als Programm umgesetzt werden. Das heißt,
die Programmierer haben im Wesentlichen für Dinge, die immer wieder dasselbe waren,
dasselbe getan ist, nochmal als Algorithmus geschrieben.
Also ein Computer ist der dumme Rechenknecht,
der nix anderes tut, als das zu tun,
was wir ihm sagen oder manchmal nicht mehr das.
Und aus der Zeit stammt eben der Mythos des dummen Computers.
Damals kann man auf die Idee Problemlöseparadigma so anzugehen,
dass man das Suchen nach dem Algorithmus auch dem Computer überlässt.
Dass Programmierer nicht mehr
programmieren können müssen
Also wir, Computer sollen so mächtig werden,
dass sie einem nicht stupide Arbeit, Probleme zu analysieren,
mit dem Algorithmus zu suchen, auch abnehmen.
Ja, also zumindest der Schritt der Kodierung und vieles auf dem Weg dahin.
Und das hat zuerst eben zu diesem General Poplim-Solver geführt.
Und später ist es ein vernünftiger Form realisiert worden in der Sprache Prolog,
die inzwischen wirklich eine vernünftige, gute Programmiersprache ist,
mit der man viel, viel anfangen kann.
Also wir wollen jetzt Und sag mal, die ersten Ideen
anschauen, wie man rangegangen ist. Dieses Programmieren,
so zu machen, dass man das Problem analysiert,
die Fakten aufzählt und die ganzen Ablaufkontrollen.
Das sollte der Computer machen. Und das ist General Problem-Solver
zum ersten Mal ein Angriff geworden, genommen worden.
Von Ellen Newel. Also der war sehr, sehr euphorisch.
It's not my eben to surprise or to shock you,
but the simplest way,
I can summarize is to say that there are know in
the volt machines,
that think that learn on
Maurova, der Abability-Todo, die Things, ist going to increase rapidly unchill in diversible Future.
Die Range von Problemen, die händeln, will co-extensiv,
ist die Range, die die Humanmintests beendet.
Also als Anonyvelle das an seiner Doktorarbeit gesagt hat,
ja, da waren die Leute wirklich sehr, sehr optimistisch.
Also was passiert ist, ist gesagt, dass das natürlich
zu dem Zeitpunkt absolut nicht erfüllt werden konnte.
Und dass in der Folge immer dann, wenn Programme,
die diese auf diese Methode zurückgriffen, erfolgreich waren,
dass die als so solide betrachtet wurden,
dass man sie aus der KI rausgenommen hat und gesagt hat,
nee, das ist ja auch schon die klassische Informatiktechnik.
Also im Moment ist die Situation für Forschung in der künstlichen Intelligenz so,
dass alles, was man für spekulativ und schwierig hält und nie erreichbar,
das ist KI.
Und sobald man es gut verkaufen kann und das kommerziell nutzbar ist,
ist es nicht mehr KI. Also solche Sprachen wie ProLog und so,
die funktionieren und mit dem gute Projekte machen kann, das ist Standardinformatik inzwischen.
Also wenn man sich mit der Forschung befasst, muss man damit leben,
dass man immer auf der Seite der Spinner steht Aber die Sachen,
die gut sind,
die gehen in die Literatur ein und werden gemacht und genutzt und sind wunderbar.
Ja, und darüber reden wir dann das nächste Mal,
was die Version von GPS ist,
also bis nächsten Freitag.
Tschüss, dann