1 / 32

Grundlagen der Informatik 1 Thema 10: Zuweisungen und andere Effekte

Grundlagen der Informatik 1 Thema 10: Zuweisungen und andere Effekte. Prof. Dr. Max Mühlhäuser Dr. Guido Rößling. Zuweisungen und andere Effekte: Übersicht. Funktionen mit Gedächtnis , Version 2 set! Beispiel : Implementierung eines Addressbuchs

Download Presentation

Grundlagen der Informatik 1 Thema 10: Zuweisungen und andere Effekte

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Grundlagen der Informatik 1Thema 10: Zuweisungen und andere Effekte Prof. Dr. Max Mühlhäuser Dr. Guido Rößling

  2. Zuweisungen und andere Effekte: Übersicht • FunktionenmitGedächtnis, Version 2 • set! • Beispiel: ImplementierungeinesAddressbuchs • Wie Zuweisungen unserer Programmiermodell ändern • Sequenzierung von Ausdrücken mit(begin ..) • Teilen, Äquivalenz und Identität • Nutzen von Zustandsvariablen zur Kommunikation • Zuweisungen und Modellierung, Performanz, Streams

  3. Wichtige Eigenschaften von Funktionen • Egal, wie oft wir eine Funktion mit ein und derselben Eingabe benutzen, wir bekommen immer das gleiche Ergebnis • An jeder Stelle eines Programms können wir einen Funktionsaufruf durch seine Definition oder seinen Wert ersetzen, ohne den Sinn des Programms zu verändern • Wir können (+ 3 5) durch 8 ersetzen • Wir können (mapsucc (list3 5)) durch ((lambda (f a-list) … ) succ (list3 5)) ersetzen, wobei … die Definition von map ist • Wir können (mapsucc (list3 5)) durch (list 4 6) ersetzen

  4. Funktionen mit Gedächtnis • Diese Eigenschaft wird manchmal Gleiches durch Gleiches ersetzen oder referentielle Transparenz genannt, dieser Programmierstil als rein funktional bezeichnet • Die Eigenschaft folgt direkt aus der Definition des Substitutionsmodells • Manchmal ist es jedoch praktischer, Funktionen mit Gedächtnis einsetzen zu können • Beispiel: Implementieren eines Zählers • Wir wollen zum Beispiel zählen, wie oft die “subst”-Funktion während der Evaluation eines Programms in unserem Interpreter aufgerufen wird Die Bedeutung einer Funktion wird vollkommen durch ihre Eingabe/Ausgabe - Relation charakterisiert

  5. Funktionen mit Gedächtnis • “Rein funktionaler” Zähler:(define (countold-count) (+ old-count 1)) • Problem: • Wie müssen die eval-Funktion modifizieren • Sie muss die aktuelle Zählung als zusätzliche Eingabe annehmen, muss die neue Zählung als zusätzliche Ausgabe liefern • Wir müssen alle Funktionen modifizieren, die eval aufrufen • eval-if, eval-app, run-program, … • Sie alle müssen einen zusätzlichen Eingabe/Ausgabe - Parameter annehmen/liefern und die entsprechenden Zähler-Werte aller eval-Aufrufe zu ihrem eigenen Ausgabewert des Zählers hinzufügen • Diese Lösung ist in höchstem Maße unmodular!

  6. Funktionen mit Gedächtnis • Was wir im Beispiel gerne hätten, ist eine Funktion mit Gedächtnis • Sie merkt sich den alten Wert des Zählers. • Wenn sie aufgerufen wird, löst sie einen Nebeneffekt (oder einfach einen Effekt) aus: sie erhöht den Wert des Zählers • Dies ist ein Nebeneffekt, weil es nicht Teil der Eingabe/Ausgabe – Relation ist

  7. Zuweisungen • Ein set!-Ausdruck, auch bekannt als Zuweisung, hat folgende Form: • (set! varexp) • Er besteht aus • Einer Variablen, der linken Seite • Einem Ausdruck, genannt rechte Seite. • Die linke Seite eines set!-Ausdrucks ist eine Konstante. • In dieser Veranstaltung verwenden wir nur Variablen, die entweder auf höchster Ebene oder in einem local-Ausdruck definiert sind. • Der Wert eines set!-Ausdrucks ist undefiniert (wie bei einem define-Ausdruck) und irrelevant. • Wichtig ist der Effekt beim Auswerten eines set!-Ausdrucks: Nach der Zuweisung werden alle Referenzen auf var zum Wert von exp evaluieren • Wechseln Sie zur Sprache “Advanced Student” in DrScheme um set! benutzen zu können.

  8. Zuweisungen • Mit Zuweisungen könnte das Zähler-Beispiel wie folgt gelöst werden: • Was bedeutet (define (increment-counter) …)? • Es bedeutet (define increment-counter (lambda () …) • Es ist eine Funktion ohne Parameter! • Wird aufgerufen mit (increment-counter) • Verwechseln Sie nichtincrement-counter und (increment-counter) • Eine Funktion ohne Parameter wäre (beinahe) nutzlos, wenn sie nur rein funktional / effektfrei ist: Sie wäre lediglich eine Konstante • “beinahe”, weil diese Technik auch dafür benutzt werden kann, um Evaluation zu verhindern ;; provide initial value for counter (define counter-value 0) ;; incrementing counter(define (increment-counter) (set! counter-value (succ counter-value))

  9. Zuweisungen

  10. Funktionen mit Gedächtnis • Ein anderes Beispiel: Führen eines Telefonbuchs • Telefonbuch-Software leitest zumindest zwei Dienste: • Ein Dienst zum Nachschlagen der Telefonnummer einer Person • Ein Dienst zum Hinzufügen eines Namens und einer Telefonnummer zum Adressbuch • Mögliche Oberfläche für die Software:

  11. Funktionen mit Gedächtnis Der zugehörige Code könnte folgendermaßen aussehen: ;; lookup : symbol -> number or false ;; to lookup the number associated with name in ADDRESS-BOOK ;; if it doesn't find name, the function produces false (define (lookup name) ...) ;; add-to-address-book : symbol number -> void ;; to add name and number to address-book (define (add-to-address-book name number) ...) (define ADDRESS-BOOK (list (list 'Adam 1) (list 'Eve 2)))

  12. Funktionen mit Gedächtnis • Stellen Sie sich nun folgende Interaktion mit DrSchemevor: • Es ist unmöglich,dies mit effektfreien Funktionen zu erreichen! • In einem effektfreien Programm geben Funktionen immer das gleiche Ergebnis für die gleichen Parameter zurück • Ohne Zuweisungen müsste add-to-address das alte Adressbuch verarbeiten und dann ein neues erzeugen, das bei zukünftigen Aufrufen von lookupweiter benutzt werden könnte. > (lookup 'Adam) 1 > (lookup 'Dawn) false > (add-to-address-book 'Dawn 4) > (lookup 'Dawn) 4

  13. Funktionen mit Gedächtnis • Lösung mit Zuweisungen • Die Verwendung von “!” in allen Funktionen, die Zuweisungen gebrauchen, ist eine sinnvolle Konvention, wird aber nicht vom Interpreter erzwungen • Wir nennen ADDRESS-BOOK eine Zustandsvariable (define (add-to-address-book! name number) (set! ADDRESS-BOOK (cons (list name number) ADDRESS-BOOK)))

  14. Wie sich unser Programmier-Modell ändert • Zuweisungen sind eine fundamentale Änderung unseres Programmiermodells! • Wichtige Invarianten (referentielle Transparenz, Konfluenz), an die wir uns gewöhnt haben, gelten nicht mehr • Plötzlich wird die Zeitzum entscheidender Faktor! • Der Zeitpunkt vor einer Zuweisung im Vergleich zum Zeitpunkt nach einer Zuweisung • Auswertungsreihenfolge wird entscheidend (keine Konfluenz mehr gegeben) • Plötzlich haben wir den Begriff der Identität! • Wir können zwei Adressbücher AB1 und AB2 haben, welche zu einem Zeitpunkt t1 den gleichen Inhalt haben, aber zu einem anderen Zeitpunkt t2 unterschiedlich sind • Das bedeutet, selbst wenn sie den gleichen Inhalt haben, sind es immer noch unterschiedliche Objekte: Sie haben eine Identität, zusätzlich zu ihrem momentanen Wert

  15. Sequenzieren von Auswertungen von Ausdrücken • Angenommen, increment-countersoll nicht nur den aktuellen Wert des Zählers herausgeben, sondern diesen auch erhöhen • Wie kombinieren wir die Ausdrücke der Zuweisung und des Zähler-Wertes? • Wir könnten eine „Kombinations“-Funktion definieren, die zwei Parameter verarbeitet und den ersten ignoriert:(define (combine x y) y) • Weil dieses Muster so gebräuchlich ist, gibt es eine spezielle Form für das Sequenzieren: (begin exp-1 ... exp-n exp) • Wertet exp-1 bis exp-n und exp in gegebener Reihenfolge aus • Liefert den Wert von exp (define (increment-counter) … (set! counter-value (succ counter-value)) … … counter-value … )

  16. Ausdrucks-Auswertungen sequenzieren • begin-Ausdrücke verwenden • Der begin-Ausdruck ist nutzlos, wenn exp-1 … exp-n keine Nebeneffekte verursachen! • Die Auswertungsreihenfolge ist wichtig: • Wenn x vor dem set!-Ausdruck ausgewertet würde, wäre das Ergebnis 3, und nicht 5 • Wir können eine Variable nicht länger durch ihren Wert ersetzen (z.B. x durch 3), weil sich ihr Wert ändern kann • Keine referentielle Transparenz • Durch das Ändern einer Definition zerstört eine Zuweisung den momentanen Wert. Wenn der Programmierer die Reihenfolge von Zuweisungen nicht sorgfältig festlegt, kann das fatal sein. (define (increment-counter) (begin (set! counter-value (add1 counter-value)) counter-value)) (define x 3) (begin (set! x (+ x 2)) x)

  17. Eingabe/Ausgabe ist eine andere Art von Effekt • Zuweisungen sind nur eine (wichtige) Art von Effekten • Eine andere Art ist die Eingabe und Ausgabe (E/A bzw. I/O) • E/A ist jede Art von Kommunikation mit der „externen“ Welt außerhalb des Programms • Benutzereingaben (Maus, Tastatur, …) • Eingaben von anderen Computern (z.B. per Netzwerk) • Ausgabe (Bildschirm, Drucker, Steuergeräte, …) • Wie die Zuweisungen ist auch E/A kein Teil des rein funktionalen Verhaltens einer Prozedur • Die Reihenfolge von E/A-Effekten ist entscheidend • Etwa die Reihenfolge, in der Seiten gedruckt werden oder in der Motoren einer Maschine an- und ausgeschaltet werden

  18. Eingabe/Ausgabe ist eine andere Art von Effekt • Nehmen Sie das Beispiel der folgenden E/A Funktion • draw-circle etc. • Weil wir bisher nicht viel über Sequenzen und Effekte wussten, „missbrauchten“ wir die and-Funktion um die Effekte zu sequenzieren • (and (draw-circle (make-posn 50 50) 100 ‘yellow)) (draw-circle (make-posn 30 30) 100 ‘green))) • Durch die Nutzung von begin werden die Effekte besser sequenziert • (begin (draw-circle (make-posn 50 50) 100 ‘yellow)) (draw-circle (make-posn 30 30) 100 ‘green)))

  19. Einige Standard E/A-Funktionen • Ausgabe des Parameters nach stdout • Als Wert nach stdout: print : any -> void • Ohne Hochkomma an Symbolen und Strings etc.:display : any -> void • Traditionelle Art, irgendwo zwischen print und display:write : any -> void • Wie write, aber mit automatischem Zeilenumbruch und Einrückung:pretty-print : any -> void • Formatierung der restlichen Argumente passend zum ersten:printf : string any ... -> void • Ausgabe eines Zeilenumbruchs: newline : -> void • Lesen von Eingaben vom Benutzer: read : -> sexp Beispiel: (begin (printf "Enter your name:") (printf "Hello ~v" (read)))

  20. Ändern von lokalen Werten • Wir können jeden definierten (define) Namen ändern • Nicht nur globale, sondern auch lokale Definitionen • Weil lokale Definitionen einmal pro Aufruf der entsprechenden Prozedur verfügbar sind, haben wir eine dynamische und unbegrenzte Anzahl von Variablen • Beispiel: Zähler mit lokalen Variablen (define (make-counter init) (local ((define counter-value init)) (lambda () (begin (set! counter-value (succ counter-value)) counter-value)))) (define c1 (make-counter 0)) (define c2 (make-counter 0)) (c1)  1 (c1)  2 (c1)  3 (c2)  1 (c2)  2

  21. Ändern von lokalen Werten • Die Anzahl von Zählern ist unbegrenzt • Beispiel: 500 Zähler erstellen • Das wäre mit globalen Variablen nicht möglich • Die Anzahl von globalen Variablen ist konstant! (define list-of-counters (build-list 500 (lambda (n) (make-counter 0))))

  22. Entwerfen von Funktionen mit Gedächtnis • Wie beeinflussen Zuweisungen unseren Entwurfsprozess? • Zustandsvariablen sollten immer eine Zweckbeschreibung (purpose statement) an der Stelle haben, an der sie definiert und initialisiert werden • Wenn eine Funktion einen Effekt hat, sollte ihr Effekt beschrieben werden ;; State Variable: ;; address-book : (listof (list symbol number)) ;; to keep track of pairs of names and phone numbers (define address-book empty) ;; add-to-address-book : symbol number -> void ;; Purpose: the function always produces (void) ;; Effect: to add (list name phone) to the ;; front of address-book

  23. Entwerfen von Funktionen mit Gedächtnis • Das Zusammenstellen von Beispielen wird schwieriger • Die Zeitpunkte müssen mit einbezogen werden • Gleichermaßen werden Tests schwieriger ;; Examples: ;; if address-book is empty and we evaluate ;; (add-to-address-book 'Adam 1), ;; address-book is (list (list 'Adam 1)). ;; ;; if address-book is (list (list 'Eve 2)) and we evaluate ;; (add-to-address-book 'Adam 1), ;; address-book is (list (list 'Adam 1) (list 'Eve 2)). ;; ;; if address-book is (list E-1 ... E-2) and we evaluate ;; (add-to-address-book 'Adam 1), ;; address-book is (list (list 'Adam 1) E-1 ... E-2). ;; Tests: (begin (set! address-book empty) (add-to-address-book 'Adam 1) (equal? '((Adam 1)) address-book))

  24. Sind Zustandsvariablen nötig? • Gibt es Programme, die nicht ohne Zuweisungen geschrieben werden können? • Nein! Jedes Programm mit Zuweisungen kann in ein äquivalentes Programm ohne Zuweisungen umgeschrieben werden • Beispiel: • Die Implementierung für lookup/add-to-address-bookkann dieselbe sein wie lookup/add für maps ;; lookup : symbol addressbook -> number or false ;; to lookup the number associated with name in ADDRESS-BOOK ;; if it doesn't find name, the function produces false (define (lookup name ab) ...) ;; add-to-address-book : symbol number addressbook -> address-book ;; to add name and number to address-book (define (add-to-address-book name number ab) ...) (lookup ‘dawn (add-to-address-book ‘dawn 123 empty))

  25. Sind Zustandsvariablen nötig? • Der Unterschied zwischen den beiden Versionen ist, dass der Aufrufer das Adressbuch kennen und es im Auge behalten muss • Wenn es viele unterschiedliche Aufrufer gibt, die sich dasselbe Adressbuch teilen sollen, kann das schwierig werden • Zustandsvariablen können simuliert werden, indem jede Funktion umgeschrieben wird, um ein Zustandsobjekt als zusätzlichen Parameter zu verarbeiten und ein (möglicherweise unterschiedliches) Zustandsobjekt als zusätzlichen Rückgabewert zurück zu liefern • Das Zustandsobjekt repräsentiert den momentanen „Zustand der Welt“ - die Werte aller Zustandsvariablen • Eine Funktion, die einem Wert etwas zuweisen möchte, kann ein anderes Zustandsobjekt zurückgeben

  26. Sind Zustandsvariablen nötig? • Das Zustandsobjekt wird dann durch das Programm gereicht • Zum Beispiel könnte (f (g 42) (h 23))wie folgt seinfür (define-struct result (value state) • Beachten Sie, wie dieses Programm eine Auswertungsreihenfolge von links nach rechts etabliert! • Diese Simulation ist sehr raffiniert, weil sie für jedes Programm mit Zustand funktioniert • Für die meisten speziellen Programme ist es weitaus weniger schmerzvoll, Zustandsvariablen loszuwerden (local ((define r1 (g 42 current-state)) (define r2 (h 23 (result-state r1)))) (f (result-value r1) (result-value r2) (result-state r2)))

  27. Zustandsvariablen als Kommunikationskanäle • Variablen ermöglichen eine neue Möglichkeit der Kommunikation in Programmen • Ohne Zustandsvariablen erfolgt jegliche Kommunikation durch Prozedur-Parameter und deren Ergebnisse • Mit Zustandsvariablen können verschiedene Programmteile, die auf eine gemeinsam benutze Zustandsvariable zugreifen, Informationen durch die Variable austauschen! • Eine Zustandsvariable ist ein Kommunikationskanal!

  28. Zustandsvariablen und Black Boxes • Die Idee einer Prozedur als Black-Box ist es, Details über ihre Implementierung zu verbergen • Aufrufende müssen nur ihre Schnittstelle kennen • Mit Zustandsvariablen wird die Schnittstelle komplexer • Es ist schwierig vorauszusagen, welche Variablen geändert werden • Änderungen an Variablen sind im Vertrag der Funktion nicht sichtbar • Benutzen Sie niemals (!!!) globale Zustandsvariablen, um Informationen von einem Funktionsaufruf zur Funktionsdefinition oder umgekehrt zu transferieren • Allgemein: Immer wenn Informationen genau so gut durch Funktionsparameter/Rückgabewerte übergeben werden können, benutzen Sie Funktionsparameter/Rückgabewerte und nicht Zustandsvariablen

  29. Zuweisungen und Performanz • In manchen Fällen kann Performanz ein Grund für Zuweisungen sein • Zuweisungen können sehr effizient auf typischer Hardware implementiert werden (so genannte “von Neumann Architektur”) • Das Ändern eines großen zusammengesetzten Werts (z.B. großer Baum/lange Liste) kann sehr teuer sein, wenn er in einem rein funktionalen Stil geändert wird • Auf der anderen Seite kann so ein zusammengesetzter Wert normalerweise auf eine nicht-funktionale (destruktive) Art in konstanter Zeit modifiziert werden

  30. Zuweisungen und Streams • Streams sind manchmal eine gute Alternative zu Zuweisungen • Wir modellieren das zeitabhängige Verhalten durch einen Stream • Beispiel: Zufallszahlen • Wir haben eine initiale Zufallszahl random-init • Wir haben eine Funktion, die aus der vorherigen Zufallszahl die nächste Zufallszahl berechnet: rand-update Version mit einem Stream (define random-numbers  (my-cons random-init               (map rand-update random-numbers))) Version mit Zuweisung (define rand  (local ((define x random-init))    (lambda () (begin (set! x (rand-update x))x))))

  31. Zustandsvariablen: The Good, the Bad and the Ugly • Zustandsvariablen bevorzugen Freiheit von Kommunikation zu Lasten von Sicherheit und Vorhersehbarkeit • Zuweisungen sind nicht per se gut oder schlecht (oder häßlich) • Sie können ein extrem mächtiges Werkzeug sein • Modularität und Performanz • Aber sie machen das Programm auch weniger verständlich und vorhersehbar • Keine Konfluenz, keine referentielle Transparenz, implizit verborgene (möglicherweise unbeabsichtigte) Kommunikation, … • Sie sollten sich dieser impliziten Kosten der Benutzung von Zuweisungen bewusst sein! • Merken Sie sich, dass jedes Programm ohne Zuweisungen geschrieben werden kann • Eine gute Daumenregel ist, dass Sie viel weniger Zuweisungen benötigen, als Sie denken!

  32. Zustandsvariablen: The Good, the Bad and the Ugly • Eine Warnung an jene, die bereits Erfahrungen mit irgendeiner Programmiersprache gemacht haben: • Verfallen Sie nicht zurück in alte (schlechte) Angewohnheiten! • Fühlen Sie sich nicht zu sicher, weil Sie nun die Mechanismen kennen, die Sie immer angewendet haben. • Betrachten Sie Berechnungen nicht hauptsächlich als Abfolge von Berechnungs- und Zuweisungsschritten! • Das skaliert nicht für große Programme • Denken Sie eher in Kategorien der Problemzerlegung und Problemkomposition • Zuweisungen werden in dieser Vorlesung u. a. deshalb erst so spät eingeführt, damit Sie diese Denkweise ablegen.

More Related