Grundlagen der Informatik 1 Thema 9: Interpreter nach dem

Slides:



Advertisements
Ähnliche Präsentationen
Algorithmen und Datenstrukturen
Advertisements

Algorithmentheorie 08 – Dynamische Programmierung (1)
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Vorlesung: 1 Betriebliche Informationssysteme 2003 Prof. Dr. G. Hellberg Studiengang Informatik FHDW Vorlesung: Betriebliche Informationssysteme Teil3.
Einführung in die Informatik: Programmierung und Software-Entwicklung
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
PL/SQL - Kurze Einführung -.
Modelle und Methoden der Linearen und Nichtlinearen Optimierung (Ausgewählte Methoden und Fallstudien) U N I V E R S I T Ä T H A M B U R G November 2011.
Design by Contract with JML - Teil 2
1 JIM-Studie 2010 Jugend, Information, (Multi-)Media Landesanstalt für Kommunikation Baden-Württemberg (LFK) Landeszentrale für Medien und Kommunikation.
WS Algorithmentheorie 02 - Polynomprodukt und Fast Fourier Transformation Prof. Dr. Th. Ottmann.
Welcome DTD. Document Type Definition Graphic Services/Everything you already know about presentations Was ist eine DTD? DTD ist eine Schemasprache.
© 2006 W. Oberschelp, G. Vossen Rechneraufbau & Rechnerstrukturen, Folie 2.1.
PL/SQL - Programmierung von Programmeinheiten. © Prof. T. Kudraß, HTWK Leipzig Gespeicherte Prozeduren – Eine Prozedur ist ein benannter PL/SQL Block,
Vorlesung: 1 Betriebliche Informationssysteme 2003 Prof. Dr. G. Hellberg Studiengang Informatik FHDW Vorlesung: Betriebliche Informationssysteme Teil2.
Vererbung Spezialisierung von Klassen in JAVA möglich durch
PKJ 2005/1 Stefan Dissmann Rückblick auf 2005 Was zuletzt in 2005 vorgestellt wurde: Klassen mit Attributen, Methoden und Konstruktoren Referenzen auf.
PKJ 2005/1 Stefan Dissmann Zusammenfassung Bisher im Kurs erarbeitete Konzepte(1): Umgang mit einfachen Datentypen Umgang mit Feldern Umgang mit Referenzen.
PKJ 2005/1 Stefan Dissmann Klassenhierarchie Person Kunde Goldkunde Lieferant Object.
Die Skriptsprache Perl (2) Wolfgang Friebel DESY Zeuthen.
Programmiermethodik SS2007 © 2007 Albert Zündorf, University of Kassel 1 Model View Controller Pattern.
Programmiermethodik SS2007 © 2007 Albert Zündorf, University of Kassel 1 6. Story Driven Modeling Gliederung: 1. Einführung 2. Objektdiagramme zur Analyse.
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Modulare Programmierung
DVG Verkettete Listen Verkettete Listen. DVG Verkettete Listen 2 Primitive Datentypen Vorteile: –werden direkt vom Prozessor unterstützt.
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
Rechneraufbau & Rechnerstrukturen, Folie 12.1 © W. Oberschelp, G. Vossen W. Oberschelp G. Vossen Kapitel 12.
1. 2 Schreibprojekt Zeitung 3 Überblick 1. Vorstellung ComputerLernWerkstatt 2. Schreibprojekt: Zeitung 2.1 Konzeption des Kurses 2.2 Projektverlauf.
20:00.
Prof. Dr. Max Mühlhäuser Dr. Guido Rößling
Die Geschichte von Rudi
„Küsse deine Freunde“ – FlexKom-App teilen
Zusatzfolien zu B-Bäumen
Grundlagen der Informatik I Thema 5: Abstraktion von Design
Telecooperation/RBG Technische Universität Darmstadt Copyrighted material; for TUD student use only Grundlagen der Informatik I Thema 0: Einführung Prof.
Einleitung.
Passive Angriffe ... nicht-invasiv.
Beweissysteme Hartmut Klauck Universität Frankfurt WS 06/
Betriebliche Aufgaben effizient erfüllen
Eine Einführung in die CD-ROM
OO implementieren Teil IV Objekte erzeugen. © René ProbstModul 226IV - 2 Von der Klasse zum Objekt Plan Bau Objekt Klasse Instanzierung Objekt Das Objekt.
Zwischenmenschliche Beziehungen
Kundenorientierung und Marktforschung
Chair of Software Engineering Einführung in die Programmierung Prof. Dr. Bertrand Meyer Lektion 14: Mehrfachvererbung.
ETS4 - Was ist neu? - Wie fange ich an? - Noch Fragen?
für Weihnachten oder als Tischdekoration für das ganze Jahr
1 Ein kurzer Sprung in die tiefe Vergangenheit der Erde.
Syntaxanalyse Bottom-Up und LR(0)
Polynome und schnelle Fourier-Transformation
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fakultät.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
NEU! 1 2. Wo kommt diese Art von Rezeptor im Körper vor?
Algorithmen und Datenstrukturen Übungsmodul 6
HORIZONT 1 XINFO ® Das IT - Informationssystem PL/1 Scanner HORIZONT Software für Rechenzentren Garmischer Str. 8 D München Tel ++49(0)89 / 540.
Robust Branch-Cut-and-Price Algorithms for Vehicle Routing Problems Christian Gruber - Johannes Reiter.
PROCAM Score Alter (Jahre)
Vorlesung Mai 2000 Konstruktion des Voronoi-Diagramms II
Symmetrische Blockchiffren DES – der Data Encryption Standard
1 (C)2006, Hermann Knoll, HTW Chur, FHO Quadratische Reste Definitionen: Quadratischer Rest Quadratwurzel Anwendungen.
Graph Pattern Semantik Michael Schmidt,
Großer Altersunterschied bei Paaren fällt nicht auf!
Zahlentheorie und Zahlenspiele Hartmut Menzer, Ingo Althöfer ISBN: © 2014 Oldenbourg Wissenschaftsverlag GmbH Abbildungsübersicht / List.
MINDREADER Ein magisch - interaktives Erlebnis mit ENZO PAOLO
1 (C)2006, Hermann Knoll, HTW Chur, FHO Quadratische Reste Definitionen: Quadratischer Rest Quadratwurzel Anwendungen.
Schutzvermerk nach DIN 34 beachten 20/05/14 Seite 1 Grundlagen XSoft Lösung :Logische Grundschaltung IEC-Grundlagen und logische Verknüpfungen.
Es war einmal ein Haus
Datum:17. Dezember 2014 Thema:IFRS Update zum Jahresende – die Neuerungen im Überblick Referent:Eberhard Grötzner, EMA ® Anlass:12. Arbeitskreis Internationale.
1 Medienpädagogischer Forschungsverbund Südwest KIM-Studie 2014 Landesanstalt für Kommunikation Baden-Württemberg (LFK) Landeszentrale für Medien und Kommunikation.
Monatsbericht Ausgleichsenergiemarkt Gas – Oktober
 Präsentation transkript:

Grundlagen der Informatik 1 Thema 9: Interpreter nach dem 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 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 (operator op-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

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” vom Anfang der Vorlesung? Der Interpreter einer Programmiersprache, der die Bedeutung der Ausdrücke in der Programmiersprache festlegt, ist auch nur ein Programm.

Terminologie Die interpretierende Sprache oder 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-Interpreter oder metazirkulären Interpreter

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)

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

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.

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 implementiert werden 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. Means: using primitives, means of combination, and means of abstraction that are particularly well suited to the problem at hand. The same idea is pervasive throughout all of engineering. For example, electrical engineers use many different languages for describing circuits. Two of these are the language of electrical networks and the language of electrical systems. The network language emphasizes the physical modeling of devices in terms of discrete electrical elements. The primitive objects of the network language are primitive electrical components such as resistors, capacitors, inductors, and transistors, which are characterized in terms of physical variables called voltage and current. When describing circuits in the network language, the engineer is concerned with the physical characteristics of a design. In contrast, the primitive objects of the system language are signal-processing modules such as filters and amplifiers. Only the functional behavior of the modules is relevant, and signals are manipulated without concern for their physical realization as voltages and currents.

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.

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

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”.

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

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 abstract syntax tree - 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

Syntax Definition der abstrakten Syntax ;; An expression exp is either ;; 1. (make-definition var val) 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)) make-definition – nur fuer variablen definitionen make-proc – zur definitiojn von prozeduren/funktionen – insbesondere fuer lambda-ausdruecke make-application – is the actual application of a previously defined procedure

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

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)))]))]))

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 AST – Abstract Syntax Tree

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 1 (list 1 4) 2 (list 4 5) 3 (list 3) 4 empty 5 (list 2 5) 6 (list 3 6) ‘CDU 36.8 ‘SPD 36.7 ‘FDP 9.4 Ein Vektor Eine Map

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

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

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)) diese version von map verwendet funktion ‘make-binding’ und 2 listen (names, values) um eine neue liste zu genieren (foldr f base list)

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)))))

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

(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])) eval = evaluation of single expressions ! cannot evaulate any define expression yet – we will do that later

Auswertung und Umgebung Zum Beispiel env = Zum Beispiel (eval (make-application (make-variable '+) (list (make-variable 'x) 3)) env)  8 ;; 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) …) ‘+ (make-primitive-procedure +) ‘x 5 …

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))])) (map (lamda (expr) …) evaluates all the operands first ! applicative order as all operands are evaluated first and then the operator is applied to the operands applikative Reihenfolge

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)))

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. Auswertungsregel für zusammengesetzte Prozeduren (eval exp env)

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) (apply f (list a b))  (f a b) Wir brauchen apply, weil wir nicht wissen, wieviele Argumente die primitive Prozedur benötigt

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) …)

Substitution Substitution innerhalb von Prozeduren, Variablen und selbst-auswertenden Ausdrü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 lambda gebundene Namen überdecken äußere Bindungen Variable wird ersetzt, falls entsprechender Eintrag in Substitutionstabelle existiert

Substitution if-clause?, application?: Einfach in Unterausdrücken ersetzen (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))]))

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

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))))

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

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)))))) Erweiterung der Umgebung Erweiterung der Ergebnisliste

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

Daten als Programme (define (factorial n)   (if (= n 1)       1       (* (factorial (- n 1)) n)))                                        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 Kippschalter und eine andere factorial Maschine. Die factorial Maschine ist unendlich, da sie eine andere factorial Maschine enthält

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

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. 6 eval 720 Zum Beispiel: wenn wir dem Evaluator die Definition von factorial übergeben, kann er Fakultäten berechnen. (define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n)))

Interpreter als universelle Maschine Unser Interpreter kann als universelle Maschine betrachtet 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.

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.

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

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) (eval expr env)) (application-operands exp)) env)] [else (error 'eval (format "Unknown expression type: ~a" exp))]))

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))]))

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 ... ]))

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 ... ]))

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

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)

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

Stream-basierte Programmierung Zunächst “bauen” wir uns Listenkonstruktoren und Selektoren In einer echten Programmiersprache würde der Interpreter diese Konstrukte natürlich bereits (effizient) zur Verfügung stellen (define testprog '((define my-cons (lambda (x y) (lambda (n) (if (= n 0) x y)))) (define my-first (lambda (p) (p 0))) (define my-rest (lambda (p) (p 1)))))

Stream-basierte Programmierung Beispiel: Wir möchten die Wurzel einer Zahl berechnen Näherungsverfahren für Berechnung von (sqrt n): Beliebige erste Schätzung s0 Nächster Schätzwert In Scheme: (define next (lambda (n) (lambda (x) (/ (+ x (/ n x)) 2)))) Mit Hilfe von Streams können wir dieses Näherungsverfahren implementieren, ohne uns über Abbruchbedingungen Gedanken machen zu müssen: Der Ausdruck (repeat (next n) a) erzeugt die (unendliche) Liste der immer besser werdenden Approximationen von (sqrt n) mit Startschätzung a: a, (f a), (f (f a)), (f (f (f a))), … wobei f = (next n) (define repeat (lambda (f a) (my-cons a (repeat f (f a))))) (define next (lambda (n) (lambda (x) (/ (+ x (/ n x)) 2))))

Stream-basierte Programmierung Unabhängig von der Erzeugung dieses Stroms von Approximationen können wir nun verschiedene Abbruchbedingungen definieren within: Gib Listenelement zurück, sobald zwei aufeinander folgende Listenelemente sich weniger als eps unterscheiden relative-within: Schranke eps wird mit derzeitigem Wert multipliziert (besser bei kleinen Werten) (define within (lambda (eps l) (if (< (abs (- (my-first l) (my-first (my-rest l)))) eps) (my-first l) (within eps (my-rest l))))) (define relative-within (* eps (abs (my-first l)))) (relative-within eps (my-rest l)))))

Stream-basierte Programmierung Zusammenbau der Funktionen und Tests Bei kleinen Werten ist sqrt-rel genauer (define sqrt (lambda (a eps n) (within eps (repeat (next n) a)))) (define sqrt-rel (lambda (a eps n) (relative-within eps (repeat (next n) a)))) (sqrt 1 0.0001 2)  1.4142156862745098039 (sqrt-rel 1 0.0001 2)  1.4142156862745098039 (sqrt 1 0.01 0.0005)  0.0250697911823893182209726... (sqrt-rel 1 0.01 0.0005)  0.0225070568342295256434089...

Stream-basierte Programmierung Bei normaler Auswertungsreihenfolge funktionieren Funktionen wie fold, map, filter automatisch auch auf Streams Mit derselben Implementierung Beispiel: (my-first (my-rest (filter prime? (enumerate-interval 10000 1000000))) Benötigt viel Zeit und Platz bei appl. Reihenfolge, bei norm. Reihenfolge wird erst bei Bedarf das nächste Listenelemt berechnet Auch möglich: wobei allnats die Liste aller natürlichen Zahlen ist (my-first (my-rest (filter prime? allnats))

Stream-basierte Programmierung Weiteres Beispiel: Alle geraden Fibonacci Zahlen Um dies in unserem Interpreter laufen zu lassen, müssen die entsprechenden Hilfsfunktionen wie filter, even? natürlich erst implementiert werden Der Interpreter selbst muss dazu allerdings nicht verändert werden! (define (f a b) (my-cons a (f b (+ a b)))) (define fibs (f 1 1)) (define evenfibs (filter even? fibs))

Zusammenfassung Alle Funktionsteile sind wiederverwendbar und kombinierbar next, within, within-rel, repeat, … Ohne Streams wäre das sehr schwierig Man kann die Abbruchbedingung nicht von der Erzeugung der Werte trennen Die “pipes-and-filters” Architektur aus T5 lässt sich auch bei unendlichen Datenströmen anwenden Außerdem werden Ergebnisse dann inkrementell berechnet Die Idee unendlicher Datenströme wird in der Praxis sehr häufig eingesetzt (z.B. Unix Kommandozeile, Java Streams) Normale Auswertungsreihenfolge muss simuliert werden Streams sind eine wichtige Alternative zum Design mit Zustand und Zuweisungen Repräsentieren “Zeit” explizit und nicht implizit wie bei Zuweisungen Die Gründe, wieso normale Auswertungsreihenfolge trotzdem in vielen Sprachen nicht benutzt wird, wurden bereits diskutiert Das Hauptargument fuer applikative Reihenfolge (und nicht normale Auswertungsreihenfolge) ist i.w. Effizienz: wenn das Argument mehr als einmal benoetigt wird (was haeufig der Fall ist) muss in der normalen Auswertungsreihenfolge das Argument mehrfach auswertet werden…