1 / 57

Grundlagen der Informatik 1 Thema 9: Interpreter nach dem Substitutionsmodell und Streams

Grundlagen der Informatik 1 Thema 9: Interpreter nach dem Substitutionsmodell und Streams. Prof. Dr. Max Mühlhäuser Dr. Guido Rößling. Rückblick: Das Substitutionsmodell. Wir haben die Sprachmechanismen von Scheme bisher informal anhand des Substitutionsmodells verdeutlicht

monte
Download Presentation

Grundlagen der Informatik 1 Thema 9: Interpreter nach dem Substitutionsmodell und Streams

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 9: Interpreter nach dem Substitutionsmodell und Streams Prof. Dr. Max Mühlhäuser Dr. Guido Rößling

  2. Rückblick: Das Substitutionsmodell • Wir haben die Sprachmechanismen von Scheme bisher informal anhand des Substitutionsmodells verdeutlicht • Zahlen, Symbole etc. sind selbstauswertend • Primitive Operationen: entsprechende Maschinenbefehle ausführen • make-struct, cons: selbstauswertend • (lambda (…) …):selbstauswertend • (define…):Erweitere die Umgebung • (local…): Herausziehen der lokalen Definitionen auf oberste Ebene, jeweils neuen Namen vergeben • (operatorop-1 …op-n): • Auswertung von operator sowie op-1 … op-n • Operator muss (lambda (x-1 … x-n) exp) ergeben • Ergebnis ist die Auswertung exp‘, wobei exp‘ sich aus exp durch Substitution der freien Vorkommen von x-1 … x-n durch die Ergebnisse der Auswertung von op-1 … op-n ergibt • Lexikalisches Scoping

  3. Rückblick: Das Substitutionsmodell Der Auswertungsprozess ist selbst ein Rechenprozess, den wir als Prozedur präzisieren und implementieren können • Erinnern sie sich an das Zitat „Programming is a Good Medium for Expressing Poorly Understood and Sloppily Formulated Ideas”vomAnfangderVorlesung? Der Interpreter einer Programmiersprache, der die Bedeutung der Ausdrücke in der Programmiersprache festlegt, ist auch nur ein Programm.

  4. Terminologie • Die interpretierende Spracheoder Basissprache ist die Sprache, in der der Interpreter implementiert ist • Die interpretierte Sprache ist die Sprache, die der Interpreter auswertet • Prinzipiell ist es möglich, in jeder Programmiersprache, die ein paar Basiskriterien erfüllt einen Interpreter für jede andere Programmiersprache zu schreiben • Sogenannte “Turing-Vollständigkeit” • Das wird von nahezu jeder universellen Programmiersprache wie Java, C, Scheme, Pascal, Perl, … erfüllt • Eines der fundamentalen Resultate der Informatik • Wenn die interpretierte Sprache und die Basissprache identisch sind, spricht man von einem Meta-Interpreteroder metazirkulären Interpreter

  5. Interpreter und Programmsemantik • Interpreter sind eine Möglichkeit, um die Bedeutung (so genannte “Semantik”) einer Programmiersprache zu definieren • Ein solcher Interpreter kann daher keine “Fehler” enthalten, weil er die Bedeutung definiert • Ein Fehler kann immer nur in Bezug auf eine Spezifikation festgestellt werden • Zum Beispiel können wir dem Symbol “+” in einem Interpreter als Bedeutung eine Multiplikationsprozedur zuweisen • Das ist kein Fehler, sondern eine mögliche Definition! • Einige Programmiersprachen werden tatsächlich offiziell durch Interpreter definiert (z.B. Scheme, SML)

  6. Interpreter und Programmsemantik • Allerdings kann die Bedeutung auch anders festgelegt werden • Informelle Sprachdefinitionen • Formale Sprachdefinitionen • Denotationelle Semantik • abstractstatemachines • … • In diesem Fall macht es wieder Sinn, von Fehlern eines Interpreters relativ zu einer Sprachdefinition zu reden

  7. Interpreter und Programmsemantik • Wenn man einen Interpreter zur Sprachdefinition benutzt, setzt man voraus, dass der Nutzer die Bedeutung der Basissprache bereits versteht. • Rekursionsanker ist meistens entweder “Prosa” (deutsch/englisch) oder die Mathematik • Ein vieldiskutiertes Problem in der Mathematik (Modelltheorie vs. Beweistheorie) und der Philosophie, aber nicht Thema von GdI-1. • Insbesondere ist ein Meta-Interpreter keine vollständige Definition einer Programmiersprache! • Ein Meta-Interpreter ist zunächst nur eine Menge rekursiver Funktionsgleichungen, die viele oder auch gar keine Lösung haben können.

  8. Warum untersuchen wir Interpreter? • “Geheimnisse“ aufdecken, wie Prozesse implementiert sind  daraus tieferes Verständnis erlangen • Wir betrachten uns selbst als Sprachentwickler und nicht nur als Nutzer einer Sprache, die andere entwickelt haben. • Einblick gewinnen, wie neue Sprachen implementiertwerden • Die Definition neuer Sprachen ist ein mächtiges Mittel, um Komplexität im Design der Entwicklung zu kontrollieren. • Neue Sprachen erhöhen unsere Fähigkeit, mit komplexen Problemen umzugehen, indem sie uns Mittel zur Verfügung stellen, mit deren Hilfe wir Probleme direkter beschreiben.

  9. Warum untersuchen wir Interpreter? • Man kann viele Programme als einen Interpreter einer Sprache betrachten. • Beispiel: ein Programm zur Berechnung von Polynomen in Scheme • Verkörpert die Rechenregeln für Polynome und implementiert sie als Operationen auf Listenstrukturen in Scheme • Wenn wir dieses Programm durch Prozeduren zum Lesen und Ausgeben von Polynomen ergänzen, haben wir den Kern einer spezialisierten Sprache für die symbolische Mathematik.

  10. Ein Interpreter nach dem Substitutionsmodell • Wir werden im Folgenden einen Meta-Interpreter für eine Teilmenge von Scheme betrachten, der in Scheme selbst implementiert wird • Explizit implementierte Konstrukte • Auswertungsreihenfolge • Substitution • Umgebung • Implizit implementierte Konstrukte • Primitive Operationen (Addition, Multiplikation, …) • Primitive Werte (Zahlen, boolesche Werte) • Rekursion, if • Speicherverwaltung • Von uns hier ignorierte Konstrukte • define-struct • local

  11. Stopp #1 auf dem Weg • Randnotiz #1: Scheme ist besonders gut geeignet, um Sprachinterpreter zu schreiben • Einfache Syntax, wenige aber mächtige Sprachkonstrukte • Randnotiz #2: Obwohl der Interpreter für Scheme geschrieben wird, enthält er die grundlegende Struktur eines Interpreters für jede Ausdrucks-orientierte Sprache, die verwendet werden kann, um Programme auf einer sequentiellen Maschine zu schreiben. • Genau genommen enthalten viele Programme (nicht nur Interpreter) tief unten einen kleinen „Scheme“ Interpreter Greenspun's Tenth Rule of Programming: “Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified bug-ridden slow implementation of half of Common Lisp”.

  12. Stopp #2 auf dem Weg • Studenten sind von Meta-Interpretern häufig verwirrt. • Verwechseln Sie nicht die Basissprache mit der interpretierten Sprache! • Wenn Sie einen Interpreter in Scheme schreiben, beeinflusst das nicht die Auswertung des DrScheme Interpreters • Wenn Ihr Interpreter bei “+” multipliziert, ergibt (+ 3 5) in DrScheme nach wie vor 8

  13. Aufbau des Interpreters • Der Interpreter hat eine ähnliche Struktur wie der Interpreter für arithmetische Ausdrücke, die wir bereits kennengelernt haben • Syntaxdefinition anhand entsprechender Datentypen • Parser – transformiert s-expressions in entsprechende Exemplare der Syntax-Datentypen (sogenannter abstractsyntaxtree - AST) • Auswertungsprozedur (eval...) • Neu hinzu kommen folgende Funktionalitäten: • Substitution bei Funktionsanwendung • Umgebung, enthält primitive Operationen und selbst definierte Namen • Startprozedur, setzt die Umgebung auf und führt ein Scheme Programm aus

  14. Syntax • Definition der abstrakten Syntax ;; An expression exp is either ;; 1. (make-definition varval) where var is a symbol and;; val is an exp ;; 2. (make-proc params exp) where params is a list-of-symbols ;; and exp is an exp ;; 3. (make-if-clause predicate c a) where predicate, c and a are exp ;; 4. (make-application operator operands) where operator is an exp ;; and operands is (listof exp) ;; 5. (make-variable n) where n is a symbol ;; 6. a number or a boolean or a string (define-struct definition (variable value)) (define-struct proc (parameters exp)) (define-struct if-clause (predicate consequent alternative)) (define-struct application (operator operands)) (define-struct variable (name))

  15. Syntax • Wir lassen einige Teile von Scheme, soweit wir es kennen, weg • local-Definitionen • define-struct • cond • Direkte Definition von Prozeduren wie(define(f x) …) • Der letzte Punkt ist keine Einschränkung, da wir lambda Ausdrücke zulassen • Mit lambda Ausdrücken (make-proc…) können wir diese Form von Prozedurdefinition leicht kodieren: (define (f x-1 … x-n) exp) wird zu (define f (lambda (x-1 … x-n) exp)) • Auch cond kann mittels if kodiert werden • Die Menge der primitiven Operationen ist in der Syntax nicht festgelegt

  16. Parser ;; parse converts an s-expression into an exp structure ;; parse : s-expression -> exp (define (parse sexp) (cond [(number? sexp) sexp] [(string? sexp) sexp] [(boolean? sexp) sexp] [(symbol? sexp) (make-variable sexp)] [(cons? sexp) (local ((define op (first sexp))) (cond [(and (symbol? op) (symbol=? op 'lambda)) (make-proc (second sexp) (parse (third sexp)))] [(and (symbol? op) (symbol=? op 'if)) (make-if-clause (parse (second sexp)) (parse (third sexp)) (parse (fourth sexp)))] [(and (symbol? op) (symbol=? op 'define)) (make-definition (second sexp) (parse (third sexp)))] [else (make-application (parse (first sexp)) (map parse (rest sexp)))]))]))

  17. Parsing und abstrakte Syntax • Die Datentypdefinitionen sind eine Abstraktion von der konkreten Syntax des Programms • So sind Klammern, Kommentare etc. verschwunden • Die konkrete Syntax zu dieser abstrakten Syntax könnte auch anders aussehen, z.B. f(op1,op2) statt (f op1 op2) oder 3+5 statt (+ 3 5) • Daher der Name abstrakte Syntax • Der Parser ist durch die Verwendung von s-expressions einfach • Ohne s-expressions erhält der Parser lediglich eine Liste von Zeichen und muss daraus die abstrakte Syntax extrahieren • Eine „Wissenschaft für sich“, aber nicht Thema von GdI-1 • Wichtig ist, dass wir anhand des ersten Elements einer Liste im Parser immer bereits entscheiden können, welchen AST-Datentyp wir erzeugen müssen • Andernfalls würde das Parsen erheblich komplexer

  18. Der abstrakte Datentyp map • Zur Implementierung des Interpreters verwenden wir einen neuen Datentyp: map • Dieser Datentyp ähnelt Vektoren, doch statt Werte mit Zahlen zu assoziieren, assoziiert er Werte mit Symbolen • Das Symbol ist der Schlüssel, der mit einem Wert assoziiert wird • Der Datentyp map wird häufig benötigt, nicht nur in Interpretern, daher hat die Implementierung dieses Datentyps nichts mit dem Interpreter zu tun Eine Map Ein Vektor

  19. Der abstrakte Datentyp map • Normalerweise würde man diesen Standard-Datentyp nicht selbst implementieren, sondern eine vorhandene Implementierung in einer Standard-Bibliothek benutzen • Wir wollen aber genauer verstehen, wie maps funktionieren • Daher implementieren wir diesen Datentyp selbst

  20. Der abstrakte Datentyp map • Konstruktoren • Selektoren • Meist gibt es noch weitere Konstruktoren, Selektoren und Prädikate ;; Data type definition for map ;; ;; A (mapof X) is either ;; 1. (map-create names values) where names ;; is a (listof symbol) and values is a (listof X) ;; 2. (map-extend var val base-env) where var is ;; a symbol, val is an X, and base-env is a (mapof X) ;; 3. (map-remove name a-map) where name is a symbol and;; a-map is a (mapof X) ;; 4. (map-remove-all names a-map) where names is a ;; (listof symbol) and a-map is a (mapof X) ;; map-lookup: symbol (mapof X) -> X or empty

  21. Der abstrakte Datentyp map • Implementierung der Konstruktoren (define-struct binding (name value)) ;; hidden from users of map ;; map-create: (listof symbol) (listof X) -> (mapof X) (define (map-create names values) (map make-binding names values)) ;; map-extend: symbol X (mapof X) -> (mapof X) (define (map-extend var val base-env) (cons (make-binding var val) base-env)) ;; map-remove: symbol (mapof X) -> (mapof X) (define (map-remove name a-map) (filter (lambda (b) (not (symbol=? name (binding-name b)))) a-map)) ;; map-remove-all: (listof symbol) (mapof X) -> (mapof X) (define (map-remove-all names a-map) (foldr map-remove a-map names))

  22. Der abstrakte Datentyp map • Implementierung des Selektors ;; map-lookup: symbol (mapof X) -> X or empty (define (map-lookup name a-map) (if (empty? a-map) empty (if (symbol=? name (binding-name (first a-map))) (binding-value (first a-map)) (map-lookup name (rest a-map)))))

  23. Der abstrakte Datentyp map • Maps gibt es in fast jeder Programmiersprache • Meist in Form von Bibliotheksfunktionen oder • (Seltener) direkt in die Sprache integriert • Unsere Implementierung ist voll funktionsfähig, aber nicht sehr effizient • Lookup kostet O(n), wobei n die Zahl der Einträge ist • Es gibt wesentlich effizientere Implementierungen • z.B. über „Hash tables“ (ermöglichen Lookup in fast konstanter Zeit) • In vielen Implementierungen können auch andere Datentypen als Symbol als Schlüssel verwendet werden • Wir können leicht unsere Implementierung verändern; alle Nutzer vom map Datentyp, die sich an das Konstruktor-Selektor Protokoll halten, funktionieren weiterhin • Der Datentyp „map“ hat nichts mit der Listenfunktion „map“ zu tun

  24. (Selbstauswertende) Werte • Betrachten wir die Werte, die der Interpreter produzieren kann (define-struct primitive-procedure (the-proc)) ;; A value val is either ;; 1. a number ;; 2. a String ;; 3. a Boolean ;; 4. (make-proc params exp) where params is a list-of-symbols ;; and exp is an exp ;; 5. (make-primitive-procedure p) where p is a Scheme procedure ;; all values are self-evaluating ;; self-evaluating :: exp -> boolean (define (self-evaluating? exp) (cond [(number? exp) true] [(string? exp) true] [(boolean? exp) true] [(proc? exp) true] ;; procedures are self-evaluating!! [(primitive-procedure? exp) true] [else false]))

  25. ZumBeispielenv = ZumBeispiel(eval (make-application (make-variable '+) (list (make-variable 'x) 3))env)  8 Auswertung und Umgebung ;; Data type env:;; An environment env is a (mapof val) ;; eval evaluates an expression exp in an environment env ;; eval : exp env -> val (define (eval exp env) …)

  26. Die Auswertungsprozedur • Grundstruktur: • Weiterleitung an Spezialprozeduren für jeden Fall ;; eval evaluates an expression exp in an environment env ;; eval : exp env -> val (define (eval exp env) (cond [(self-evaluating? exp) exp] [(variable? exp) (eval-var exp env)] [(if-clause? exp) (eval-if exp env)] [(application? exp) (eval-app (eval (application-operator exp) env) (map (lambda (expr) (eval expr env)) (application-operands exp)) env)] [else (error 'eval (format "Unknown expression type: ~v" exp))])) applikative Reihenfolge

  27. Die Auswertungsprozedur • Auswertung von Variablen • Auswertung von if Anweisungen ;; evaluates a variable by looking it up in the environment ;; eval-var :: exp env -> val (define (eval-var exp env) (local ((define val (map-lookup (variable-name exp) env))) (if (empty? val) (error 'eval-var (format "Unbound variable: ~v" (variable-name exp))) val))) ;; eval-if : exp env -> val (define (eval-if exp env) (if (eval (if-clause-predicate exp) env) (eval (if-clause-consequent exp) env) (eval (if-clause-alternative exp) env)))

  28. Die Auswertungsprozedur • Auswertung von Funktionsanwendungen ;; eval-app: val (listof exp) env -> val (define (eval-app procedure arguments env) (cond [(primitive-procedure? procedure) (apply (primitive-procedure-the-proc procedure) arguments )] [(proc? procedure) (eval (subst (map-create (proc-parameters procedure) arguments) (proc-exp procedure)) env)] [else (error 'eval-app (format "Procedure expected, found: ~v" procedure))])) vgl. Auswertungsregelfür zusammengesetzteProzeduren

  29. Die Auswertungsprozedur • Die Prozedur „subst“ ist zunächst einmal auf unserer Wunschliste und wird als nächstes implementiert • Die Prozedur „apply“ die innerhalb der Auswertungsprozedur benutzt wird, ist eine primitive Prozedur, mit der die Parameter einer Prozedur als Liste übergeben werden können • (apply+ (list 1 2 3)) (+ 1 2 3) • (applyf (list a b))  (f a b) • Wir brauchen apply,weil wir nicht wissen, wieviele Argumente die primitive Prozedur benötigt

  30. Substitution • Substitution wird als rekursive Funktion über Ausdrücken definiert • Fallunterscheidung je nach Typ des Ausdrucks, wie bei eval • Die zu ersetzenden Namen und Werte werden in einer map übergeben ;; subst substitutes all occurences of names in exp that are;; associated with a value in a-map with that value, according ;; to the lexical scoping rules ;; subst : (mapof val) exp -> exp (define (subst a-map exp) …)

  31. Substitution • Substitution innerhalb von Prozeduren, Variablen und selbst-auswertendenAusdrücken (define (subst a-map exp) (cond [(proc? exp) (make-proc (proc-parameters exp) (subst (map-remove-all (proc-parameters exp) a-map) (proc-exp exp)))] [(self-evaluating? exp) exp] [(variable? exp) (local ((define newval (map-lookup (variable-name exp) a-map))) (if (empty? newval) exp newval))] ...)) Lokal mit lambdagebundene Namen überdecken äußere Bindungen Variable wird ersetzt, falls entsprechender Eintrag in Substitutionstabelle existiert

  32. Substitution • if-clause?, application?: • Einfachin Unterausdrückenersetzen (define (subst a-map exp) (cond ... [(if-clause? exp) (make-if-clause (subst a-map (if-clause-predicate exp)) (subst a-map (if-clause-consequent exp)) (subst a-map (if-clause-alternative exp)))] [(application? exp) (make-application (subst a-map (application-operator exp)) (map (lambda (op) (subst a-map op)) (application-operands exp)))] [else (error 'subst (format "Unknown expression type: ~v" exp))]))

  33. Substitution • Die Substitutionsprozedur enthält noch einen Fehler • Wenn der Ausdruck, der eingesetzt wird, eine freie Variable enthält, wird diese möglicherweise durch die Substitution fälschlicherweise gebunden • Wir werden diesen Fehler (noch) nicht beheben • Die Lösung ist etwas kompliziert • Basisidee: Vor der Substitution in einem Lambda-Ausdruck wird geprüft, ob einer der Parameternamen frei in einem der Ausdrücke in der Substitutionstabelle vorkommt • Falls ja, wird der Parameter umbenannt

  34. Primitive Prozeduren • Die gewünschten primitiven Operationen bilden die initiale Umgebung • Kann leicht um neue primitive Operationen erweitert werden • Es besteht kein Zwang, dass eine primitive Operation durch die primitive Operation desselben Namens in der Basissprache implementiert wird • Wir können auch nicht-primitive Scheme Prozeduren benutzen, die dann in der interpretierten Sprache primitiv sind • Wir könnten hier festlegen, dass das Symbol ‘+ in unserer Sprache Multiplikation bedeutet (define primitive-procedures (local ((define names '(first rest cons list empty? empty * / - + < = abs)) (define procs (list first rest cons list empty? empty * / - + < = abs))) (map-create names (map make-primitive-procedure procs))))

  35. Ausführen von Programmen • Die eval Prozedur kann nur einzelne Ausdrücke auswerten, jedoch keine (define…) Anweisungen • Ein Scheme Programm besteht jedoch aus einer Reihe von Definitionen und einer Reihe von Ausdrücken • Die Ausführung eines Programms wird von der Prozedur run-prog erledigt • Erstellt initiale Umgebung • Erweitert Umgebung nach Auswertung einer (define…) Anweisung • Für andere Ausdrücke werden die jeweiligen Ergebnisse berechnet und zu einer Ergebnisliste zusammengefügt

  36. Ausführen von Programmen ;; a program prog is a (listof exp) ;; run-prog :: prog env -> (listof val) (define (run-prog prog env) (if (empty? prog) empty (local ((define exp (parse (first prog)))) (if (definition? exp) (run-prog (rest prog) (map-extend (definition-variable exp) (eval (definition-value exp) env) env)) (cons (eval exp env) (run-prog (rest prog) env)))))) Erweiterungder Umgebung Erweiterungder Ergebnisliste

  37. Benutzung des Interpreters (define testprog '((define ! (lambda (n) (if (= n 0) 1 (* n (! (- n 1)))))) (! 5))) (run-prog testprog primitive-procedures)

  38. Daten als Programme • Wir können factorial als Beschreibung einer Maschine verstehen, die Teile zum subtrahieren, multiplizieren und testen auf Gleichheit enthält. • Darüber hinaus enthält die Maschine einen Kippschalterund eine andere factorial Maschine. • Die factorial Maschine ist unendlich, da sie eine andere factorial Maschine enthält (define (factorial n)  (if (= n 1)      1      (* (factorial (- n 1)) n)))

  39. Interpreter als universelle Maschine 1 1 = 6 720 * - factorial 1 Die factorial Maschine

  40. Interpreter als universelle Maschine Analog können wir einen Interpreter als eine sehr spezielle Maschine ansehen, die als Eingabe die Beschreibung einer anderen Maschine erwartet. Über diese Eingabe konfiguriert sich der Interpreter selbst, so dass er die in der Eingabe beschriebene Maschine nachbildet. eval 6 720 (define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n)))

  41. Interpreter als universelle Maschine • Unser Interpreter kann als universelle Maschinebetrachtet werden. • Er imitiert andere Maschinen, wenn sie als Scheme Programme beschrieben sind. • Stellen Sie sich einen analogen Interpreter für elektrische Schaltkreise vor • … ein Schaltkreis, der als Signal die Pläne für andere Schaltkreise empfängt, z.B. ein Filter. Basierend auf diesem Input verhält sich der Schaltkreis wie ein Filter. • So ein universeller elektrischer Schaltkreis ist so komplex, dass er kaum vorstellbar ist. • Dagegen ist es überraschend, wie einfach der Code des Programm-Interpreters ist.

  42. Daten als Programme • Der Interpreter ist eine Brücke zwischen Datenobjekten, die von unserer Programmiersprache manipuliert werden, und der Programmiersprache selbst. • Unser Interpreter-Programm läuft und ein Nutzer gibt Ausdrücke an und beobachtet die Ergebnisse… • Aus dem Blickwinkel des Nutzers ist die Eingabe (* x x) ein Ausdruck in der Programmiersprache, den der Interpreter ausführen soll. • Vom Blickwinkel des Interpreterprogramms ist der Ausdruck nur eine Liste von Symbolen, *, x, und x, die nach festgelegten Regeln manipuliert werden sollen.

  43. Normale Auswertungsreihenfolge • Mit Hilfe des Interpreters können wir nun sehr präzise ausdrücken, was normale Auswertungsreihenfolge ist und wie sie sich von applikativer Reihenfolge unterscheidet • Informal: Bei normaler Auswertungsreihenfolge wird nur der Operator ausgewertet, die Operanden werden hingegen unausgewertet in den Funktionskörper hineinsubstitutiert, nur bei primitiven Operationen auswerten

  44. Der Unterschied… • eval Funktion bei applikativer Reihenfolge (define (eval exp env) (cond [(self-evaluating? exp) exp] [(variable? exp) (eval-var exp env)] [(if-clause? exp) (eval-if exp env)] [(application? exp) (eval-app (eval (application-operator exp) env) (map (lambda (expr) (evalexprenv)) (application-operands exp)) env)] [else (error 'eval (format "Unknown expression type: ~a" exp))]))

  45. Der Unterschied… • eval Funktion bei normaler Reihenfolge (define (eval exp env) (cond [(self-evaluating? exp) exp] [(variable? exp) (eval-var exp env)] [(if-clause? exp) (eval-if exp env)] [(application? exp) (eval-app (eval (application-operator exp) env) (application-operands exp) ;; normal order env)] [else (error 'eval (format "Unknown expression type: ~a" exp))]))

  46. Der Unterschied… • Die eval-app Funktion bei applikativer Reihenfolge (define (eval-app procedure arguments env) (cond [(primitive-procedure? procedure) (apply (primitive-procedure-the-proc procedure) arguments )] [(proc? procedure) ... ] [else ... ]))

  47. Der Unterschied… • Die eval-app Funktion bei normaler Reihenfolge (define (eval-app procedure arguments env) (cond [(primitive-procedure? procedure) (apply (primitive-procedure-the-proc procedure) (map (lambda (expr) (eval expr env)) arguments) )] [(proc? procedure) ... ] [else ... ]))

  48. Vorteile von normaler Auswertungsreihenfolge • Wir brauchen weniger Sonderformen! • Macht den Interpreter einfacher • Jede Sonderform erfordert eine eigene Behandlung im Interpreter • Macht die Sprache einfacher • Weniger Regeln/Ausnahmen • Macht die Sprache mächtiger • So kann fold auch mit or/and benutzt werden • Beispiel: if, cond, or, … müssen keine Sonderformen mehr sein, sondern können ganz “normale” Funktionen sein

  49. Vorteile von normaler Auswertungsreihenfolge • Beispiel: if als normale Funktion • Dieses Beispiel würde bei applikativer Reihenfolge nicht funktionieren (define testprog '((define my-if (lambda (c t e) (if c t e))) (define ! (lambda (n) (my-if (= n 0) 1 (* n (! (- n 1)))))) (! 5))) (run-prog testprog primitive-procedures)  (list 120)

  50. Streams • Normale Auswertungsreihenfolge ermöglicht zudem eine elegante Programmiertechnik: Stream-basierte Programme • Modellieren von zeitbehafteten Objekten ohne Zuweisungen • Idee: Beschreibe zeitabhängiges Verhalten eines Objektes als (unendliche) Sequenz x1, x2,… • Man kann sich diese Sequenz als Repräsentation einer Funktion x(t) vorstellen • Wir werden Streams als (unendliche) Listen darstellen. • Funktionen arbeiten auf unendlich großen Strömen von Daten

More Related