2.3 Einblick in die Implementierung funktionaler Sprachen

Werbung
2.3 Einblick in die Implementierung
funktionaler Sprachen
Vorgehen:
• Sprachmittel funktionaler Programmiersprachen
• Ein Interpreter für eine einfache funktionale Sprache
• Übersetzung einer einfachen funktionalen Sprache
2.3.1 Sprachmittel funktionaler
Programmiersprachen
Funktionale Programmierung im Überblick:
• funktionales Programm:
- partielle Funktion von Eingabe- auf Ausgabedaten,
- besteht aus Deklarationen von Datentypen und
Funktionen, insbesondere Funktionen höherer
Ordnung (s.u.)
- Rekursion ist eines der zentralen Sprachkonzepte
- kein Zustandskonzept, keine veränderlichen
Variablen, keine Schleifen, keine Zeiger
• Ausführung eines funktionalen Programms:
Anwendung der Funktion auf Eingabedaten
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
107
Definition:
(partielle Funktion)
Ein Funktion heißt partiell, wenn sie nur auf einer
Untermenge ihres Argumentbereichs definiert ist.
Andernfalls heißt sie total.
Bemerkungen:
• Da Terminierung nicht entscheidbar ist, definiert
man in der Informatik häufig nur partielle Funktionen.
• Durch Einführen eines Wertes für „undefiniert“ kann
man jede partielle Funktion total machen.
Üblicherweise bezeichnet man den Wert für
„undefiniert“ mit ⊥ (engl. „bottom“).
Definition:
(strikte Funktion)
Ein Funktion f heißt strikt, wenn f(⊥) = ⊥ .
Klassifikation funktionaler Programmiersprachen:
strikt/eager
nicht-strikt/lazy
typisiert
ML
Haskell
untypisiert
Lisp
( OLisp )
Wir betrachten im Folgenden nur ML.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
108
Es gibt drei Arten von Werten bzw. Typen, mit
denen gerechnet werden kann:
• Basisdatentypen (int, bool, string, ...)
• rekursive Datentypen
• Funktionstypen
In funktionalen Programmiersprachen werden
Funktionen also auch als Werte betrachtet.
Werte werden mit Ausdrücken beschrieben.
„Variablen“ werden benutzt, um Werte zu bezeichnen.
Die funktionale Programmiersprache ML
Vernachlässigt man die Modularisierungskonstrukte,
besteht ein ML-Programm aus
• der Einführung von Bezeichnern für Werte:
- val
x = 7;
• der Definitionen von Typen:
-
type t = ... ;
-
datatype dt = ... ;
ML bietet ein interaktives Laufzeitsystem, das
Eingaben obiger Form akzeptiert. Selbstverständlich
kann man Eingaben auch aus einer Datei einlesen.
Darüber hinaus gibt es Übersetzer für ML.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
109
Beispiel: (Werte von Basisdatentypen)
- val x = 7;
- val y = 9;
- val z = x + y ;
Bei Datentypen antwortet das Laufzeitsystem immer
mit dem berechneten Wert und dem Typ:
val z = 16 : int
Funktionen - Anwendung und Beschreibung:
Funktionen sind in ML immer einstellig.
Die Anwendung (application) einer Funktion f auf
.
ein Argument e wird notiert als
Funktionen werden mittels λ-Abstraktion
beschrieben, d.h.:
- val f = fn x => A ;
Hier wird f als Bezeichner für eine Funktion eingeführt;
d.h. fn x => A beschreibt eine Funktion.
Welche Funktion beschreibt fn x => A ?
Auswertungssemantik:
Den Wert von (fn x=>A) e erhält man, indem man
• das Argument auswertet; Ergebnis sei der Wert • x durch z in A ersetzt und
• den resultierenden Ausdruck auswertet.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
110
Beispiele: (Listen und Tupel in ML)
- val l1
- val l2
- val l3
val l3 =
= [ 2,3,4 ] ;
= [7,9] ;
= l1 @ l2 ;
[2,3,4,7,9] : int list
- val le = (1,"1.String",false);
val le = (1,"1.String",false):int*string*bool
- val re = ( 3 );
val re = 3 : int
- val re = (3,7);
val re = (3,7) : int * int
- val tup = (le,re);
val tup = ((1,"ersterString",false),(3,7))
: (int * string * bool) * (int * int)
Mit der Tupelbildung lassen sich „baumstrukturierte“
Werte, sogenannte Tupelterme, aufbauen.
So entspricht der Tupelterm:
dem Baum:
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
111
Rekursive Datentypen:
Rekursive Datentypdeklarationen deklarieren
Mengen von baumstrukturierten, typisierten Werten
(ähnlich unserer Notation für abstrakte Syntax).
Folgende Datentypdeklaration deklariert den Typ
! " E
mit der Konstanten B C D
und
# $ % den Konstruktorfunktionen
und
:
% E
! " F
D
E
G
B C D
$ G
# $ % $ ! " &
! " H
Typen der Konstruktorfunktionen:
! " # $ % ! " &
! " ! " Mit den Konstruktorfunktionen lassen sich typsicher
„baumstrukturierte“ Werte, sogenannte
Konstruktorterme, aufbauen:
'
( ) *
+ ,
-
. / 0 1
2 3 1 ) 4
. / 0 1
( ) *
+ ,
-
. / 0 1
2 3 1 ) 4
5 6
2 . / 0 1
5 6. / 0 1
2 3 1 ) 4
2 . / 0 1
7 6 3 1 ) 4
<
6 3 1 ) 4
8 9 6 3 1 ) 4
< 9 9 =
: 9 9 ;
, > ? @ A 1 1
Listen werden in ML auch als Konstruktorterme
I
aufgefasst und zwar mit der Konstanten
und dem Infixkonstruktor J J .
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
112
Beispiel: (nicht-rekursive Funktionen in ML)
X
Y Z
X
Y Z
Y Z
L
L
[
\
[ Q
L
b
\
[
b
\
U d
]
c
S
\ ^
] _ ` a
a
M Q O
a
Listen- und Tupeltypen:
ML unterstützt Typkonstruktoren zur Definition
von Listen- und Tupeltypen. Sind S und T Typen,
dann bezeichnet:
•
K
L M N O
• S*T
den Typ der Listen mit Elementen vom Typ
den Typ der Tupel mit erstem Element vom
Typ K und zweitem Element vom Typ P .
Die leere Liste wird mit Q M L bezeichnet,
das Anhängen vorne an eine Liste mit R
X
Y Z
L
M L
\
`
S S
Q M L
Y Z
L
M L
\
e
f V
U d
V
:
a
Eine Liste mit den Elementen
[ T U V W W W V T Q ] geschrieben.
X
S SR
g d h
X
V
T U
, ... , T Q wird als
h
i
a
Ein Tupel mit Elementen T U , e2 wird als ( T U V T f
geschrieben. Tupel mit mehr als zwei Elementen
sind auch zulässig.
X
Y Z
L
14.12.2005
O k l
\
m
n
V
O o k T
j
j
a
© A. Poetzsch-Heffter, TU Kaiserslautern
113
K
Pattern Matching:
Bisher haben wir nur gezeigt, wie Terme aufgebaut
werden, aber nicht, wie man auf ihre Teile zugreifen
kann.
ML arbeitet ohne Selektoren und nutzt zum Selektieren
und für viele andere Aufgaben Pattern Matching auf
Tupel- und Konstruktortermen.
Idee:
Terme mit Variablen (Pattern) werden so über
variablenfreie Terme gelegt, dass die Wurzel der Terme
zusammenfallen. „Passt“ das Muster/Pattern (match),
werden die Variablen an die zugehörigen Teilterme
gebunden.
Beispiel:
(Pattern Matching)
Seien x, y Variablen; dann passt
auf p p q r s t u v w r p p x y u z v { x r x | } ~ 
q
„
…
„
†
s t u v
…
†
p x y u z v { x r x | } ~  x r x s € { { x w
14.12.2005
p p q
r„ w r p …
r x  ‚  x w w
x r x s € { { x w r x  ‚  x w w ƒ
x  ‚  x
© A. Poetzsch-Heffter, TU Kaiserslautern
114
Pattern Matching auf Konstruktortermen ist
entsprechend definiert. Selbstverständlich lassen
sich Tupel- und Konstruktorterme kombinieren.
Definition von Funktionen - Weitere Aspekte:
Durch die Verwendung von Tupeln und von Pattern
Matching lassen sich in ML auch „mehrstellige“
Funktionen realisieren:
‡
ˆ ‰ Š
ˆ ‰ Š
‡
‰ ‹ ‹ Œ
ˆ ‰ Š
ˆ ‰ Š
‰ ‹ ‹ Œ

 ž Ÿ
 ž Ÿ


Ž 
Ž 
™

‰ ‹ ‹ Œ
¢
™
 ‘
’“
š  ›
 Œ
’” •
œ
 –
š  ›
‘ — “ — ”
œ
š  ›
˜
‡ –
š  ›
˜
’ ’¡ • ˜
š  ›
Funktionen höherer Ordnung sind Funktionen, die
Funktionen als Argumente oder Ergebnisse haben:
‡
ˆ ‰ Š
ˆ ‰ Š
‡
‡
‰ £ £ Š › ¤ š ¥ 
ˆ ‰ Š
ˆ ‰ Š
§ ¨ ¥ ¥
§ ¨ ¥ ¥
ˆ ‰ Š
ˆ ‰ Š
‰ £ £ Š › ¤ š ¥ 

 ž Ÿ
 ž Ÿ



Ž 
Ž 
™

Ž 
Ž 
‘
 Ž
™
 ¦ ‰
 –
š  ›
’ ‘ •
‡ –
 –
Ž
¦‰ •
 Ž
œ
‘ • ˜
¦‰
‡ –
¦‰
‘ — © ˜
‡ –

‰ £ £ Š › ¤ š ¥ 
ª
™
š  ›
 § ¨ ¥ ¥
’Œ • ˜
š  ›
(Beachte die Variablen im Typ von appltwice.)
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
115
Ein ähnliches Beispiel, bei dem Funktionen als
Ergebnisse illustriert werden:
«
¬ ­ ®
¬ ­ ®
«
¯ ° ± ² ³
¯ ° ± ² ³
¬ ­ ®
¬ ­ ®
«
´
¬ ­ ®
³ Á Â
³ Á Â
´
µ ¶
µ ¶
¾ ¿ ² ² À
¾ ¿ ² ² À
¬ ­ ®
´
´
µ
¼
´ ·
¸ ½­
¯ ° ± ² ³
´
µ ¶
´
¾ ¿ ² ² À
à Å
¼
¼
¸ µ ¶
« ·
¾ ¿ ² ²
± ¶ ¯
« ·
¹
´ ·
µ
¸ µ
½­ º
« ·
¸ ½­
¹ º º »
« ·
½­ º
»
± ¶ ¯
Ã Ä »
± ¶ ¯
Statt der in der obersten Zeile verwendeten Syntax
unterstützt ML auch eine kürzere und flexiblere Syntax,
für die Funktionsdefinition:
«
µ ¿ ¶
¬ ­ ®
¯ ° ± ² ³
¯ ° ± ² ³
´
µ
¹
µ ¶
´
¼
µ
¸ µ
¸ ½­
¹ º »
« ·
½­ º
« ·
¸ ½­
« ·
½­ º
Bei dieser Form der Funktionsdefinition kann Pattern
Matching als Fallunterscheidung verwendet werden.
Dieses scheinbar einfache Sprachmittel vereinfacht
die Definition von Funktionen zum Teil erheblich.
Wir erläutern den Mechanismus hier nur exemplarisch.
Selektoren für Tupel:
«
µ ¿ ¶
¬ ­ ®
«
µ ¾ ¯
¬ ­ ®
¬ ­ ®
µ ¾ ¯
³ Á Â
³ Á Â
14.12.2005
´
´
¸ ¹
ÆÇ º
µ ¶
¼
´
µ ¾ ¯
´
¹
½­
È
»
½É
« ·
½­
¸ Ê ³ Á ¾ ¯ ³ ¾ Ë ® ³ Ì Ê Æ Ê À
Ê ³ Á ¾ ¯ ³ ¾ Ë ® ³ Ì Ê
¼
Í Ë ® ³ Ì Ê º »
¾ ¯ Á ± ¶ Â
© A. Poetzsch-Heffter, TU Kaiserslautern
116
Selektoren für rekursive Datentypen:
Î
Ï Ð Ñ
Þ
ã ß Ô
Î
Ò Ó Ô Ô Ó Ï Õ
Ö × Ø Ù Ó
Ò Ó Ô Ô Ó Ï Õ
Õ
Ò Ó Ô Ô Ó Ï Õ
Ï Ð Ñ
Þ
Ý
Ï Ñ
ä
Ò Ó Ô à Ñ Õ
Ö æ Ó ß Ï
Ò Ó Ô à Ñ Õ
Õ
Ö Ô Ú Û Ü Ü
á à Ñ Õ Û Ó Ó
ç Ü
Ý
Ô
Ý
Û ß à Ò Ó
á à Ñ Õ Û Ó Ó
Î å
á à Ñ Õ Û Ó Ó
Ý
ç
Ý
Û ß à Ò Ó
á à Ñ Õ Û Ó Ó
â
â
Pattern Matching kann auch für Listenargumente
verwendet werden. Als Beispiel betrachten wir
die Funktionen map, die eine einstellige Funktion
auf alle Elemente einer Liste anwendet:
Î
Ï Ð Ñ
Þ
ã ß Ô
Î
Ï
ê ë
è ß é
Ï
Ö ç
è ß é
ã ß Ô
ã ß Ô
è ß é
Ý
Ó Û í
Ó Û í
Ý
Ï Ñ
Ý
ê ñ
Ý
ê ë
ä äç Ò Ü
ä
Ö ìß
è ß é
Ý
Î å
Ò Ð î î ï
Ú ò Úó ë
ä
Ö Ï
ç Ü ä ä Ö è ß é
ìá Ü
ê ï
à Ñ Õ
Úð
Î å
ìß
Ï
ç Ò Ü
Ô à Ò Õ
Î å
â
ìá
Ô à Ò Õ
Úñ ë â
Ô à Ò Õ
Die Notation mit dem Schlüsselwort fun erlaubt
auch die Definition von rekursiven Funktionen:
Î
Ï Ð Ñ
ã ß Ô
Î
Ï ß î
ã ß Ô
ã ß Ô
Ï ß î
Ó Û í
Ó Û í
14.12.2005
Ý
Ý
Ñ
Ï Ñ
Ý
Ý
à Ï
ä
Ï ß î
Ñ Ý ô
à Ñ Õ
ö ï
Õ õ Ó Ñ
Î å
à Ñ Õ
ä
à Ñ Õ
ö
Ó Ô Ò Ó
Ñ
÷
Ï ß î Ö Ñ Î ö Ü â
â
ñ ø ù ô ô ö ó ô ô
© A. Poetzsch-Heffter, TU Kaiserslautern
117
Alternativ lässt sich die Fallunterscheidung auch
mit Pattern Matching realisieren:
ú
û ü ý
û þ ÿ
þ û þ ÿ
û þ ÿ
þ ú
þ ý
ý
û ý
û þ ÿ
ý û þ ÿ
ý ú ú ý ý Rekursive Datentypen, rekursive Funktionen und
Pattern Matching erlauben eine sehr kompakte
Programmierung auf Termen. Als erstes Beispiel
betrachten wir eine Funktion, die in einem Bintree
die Summe aller Blattbewertungen berechnet:
ú
û ü ý
þ ú
ú
þ 14.12.2005
ü ü þ û
ü ü ü ü ü ü þ þ ü ü !
û ý
! ý þ û
þ û
ü ü !
ú ü ü ý þ û
" þ û
ý © A. Poetzsch-Heffter, TU Kaiserslautern
118
Lokale Zwischenergebnisvereinbarungen:
ML unterstützt verschiedene Möglichkeiten zur Vereinbarung von Bezeichnern für Zwischenergebnisse.
Wir benötigen nur die Let-Vereinbarung:
#
$ % &
$ % &
'
'
(
& ) *
(
3 4
5
$ % &
+
(
,
-
) . /
+ 0
+ - +
) 0 1
2
+ 0 *
Deklaration von Typnamen:
Oft verbessert es die Lesbarkeit von Programmen
für „existierende“ Typen (d.h. für Typen die
bereits definiert sind bzw. mit Typkonstruktoren
aufgebaut sind), neue Namen einzuführen.
ML bietet dafür die Typdeklaration:
#
* 6 7 )
$ % .
(
8 * . + 0 /
2
#
* 6 7 )
+ 0 * * 9 7 & )
(
+ 0 *
#
* 6 7 )
; < < & & + 8 *
(
; < < &
:
+ 0 *
2
& + 8 *
2
Deklariert werden nur neue Namen, keine neuen
Typen. Dies zeigt folgendes Beispiel:
#
1 % * % * 6 7 )
1 % * % * 6 7 )
14.12.2005
; < '
; < '
(
(
= < ' + 0 /
= < ' + 0 /
< >
< >
+ 0 * * 9 7 & )
+ 0 *
:
2
+ 0 *
© A. Poetzsch-Heffter, TU Kaiserslautern
119
Polymorphie:
Parametrischer Polymorphismus ist bei typisierten
funktionalen Programmiersprachen eines der
interessantesten Sprachmittel. Die Typen
charakterisieren Eigenschaften der Werte, so dass gilt:
„Wohltypisierte Programme laufen nicht in die Irre.“
Parametrischer Polymorphismus erlaubt es:
• Programme möglichst allgemein zu typisieren:
? @ A
B
C D
E
F G@
H I
GJ K
H I
G@
L M N O
H I
GJ
L M N O
.
• Explizite Typparameter einzuführen:
H
P @ O @ O Q A R
T R @ C
V
W
U P R
G@
U C
G@
U C
G@
J M D O S R R
J M D O S R R
B
X
G@
J M D O S R R
Y
Typsystem ist im Detail recht komplex; Beispiel:
let val id = fn x => x in ( id 7 , id true ) end
ist wohltypisiert, nicht aber der operationell
äquivalente Ausdruck
( fn id => ( id 7, id true ) ) ( fn x => x )
Bemerkungen:
• Typanalyse basiert auf mächtiger Typinferenz.
• Typen sind ein wertvolles Dokumentationsmittel.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
120
Beispiel: (Ein einfacher Interpreter)
Um das Zusammenwirken der Sprachmittel zu studieren,
betrachten wir einen Interpreter für eine einfache
Ausdruckssprache:
datatype exp =
Int of int
| Add of exp * exp
| Var of string
| Let of string * exp * exp
;
val mp =
Let ("x",Add (Int 1, Int 2),
Let ("y",Int 5,
Let ("z",Int 8, Add (Add
(Var "x",Var "y"), Var "z"))));
type env = (string * int) list;
fun lkup s []
= 0
| lkup s ((s1,i)::es) =
if s=s1 then i else lkup s es ;
fun eval (Int i) E
= i
| eval (Add (le,re)) E =
(eval le E) + (eval re E)
| eval (Var s) E
= lkup s E
| eval (Let (s,e1,e2)) E =
eval e2 ((s,eval e1 E)::E) ;
val erg = eval mp [];
val erg = 16 : int
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
121
2.3.2 Ein Interpreter für eine einfache
funktionale Sprache
Ziele:
• Nutzung funktionaler Programmierung zur
Implementierung von Programmiersprachen
• Einführung in die Interpretation funktionaler
Programme
• Vorbereitung des folgenden Übersetzungsabschnitts (Funktionsabschluss)
Rekursive Datentypen und rekursive Funktionen
bieten eine gute sprachliche Basis für die
Implementierung von Programmiersprachen.
Behandlung von Funktionen als Werte
Demonstration anhand einer einfachen funktionalen
Sprache, genannt TinyML. Dabei gehen wir von
kontextkorrekten abstrakten Syntaxbäumen aus.
TinyML besitzt alle zentralen Konstrukte funktionaler
Sprachen außer rekursiven Funktionen.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
122
Ein TinyML-Programm ist ein Ausdruck mit folgender
abstrakten Syntax (Vorlesungsnotation):
Expr
=
|
Int
(
Bool (
Con
(
Var
(
UnOp (
BinOp (
Cond (
Abs
(
App
(
Let
(
ExprList
Int | Bool | Con |
BinOp | Cond | Abs |
int
)
bool )
ident, ExprList )
ident )
oprnd, Expr )
oprnd, Expr, Expr )
Expr, Expr, Expr )
ident, Expr )
Expr, Expr )
ident, Expr, Expr )
* Expr
Var
App
|
|
UnOp
Let
wobei ident und oprnd durch String repräsentiert sind.
Die Produktion mit Konstruktor Con beschreibt
die Anwendung eines Konstruktors in TinyML:
•
ident repräsentiert den Konstruktorbezeichner.
•
ExprList repräsentiert die Argumente.
Kern des Interpreters wird eine Funktion eval sein,
die einen Ausdruck auswertet. Fragen:
1. Wie werden Variablen ausgewertet?
2. Wie werden Abstraktionen ausgewertet?
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
123
Ad 1: Wir benutzen eine Werteumgebung, die
Bezeichnern Werte zuordnet.
Ad 2: Zerfällt in zwei Probleme:
• Was soll z.B. Z [ \ ] ^ _ ` a ^ b c b d e f g h i ^ j b k b d b c b d l m
liefern, wobei E eine beliebige Umgebung ist?
d
n m
• Wie soll man nicht-lokale Variablen behandeln?
Beispiel: (Nicht-lokale Variablen in ML)
v
w x y
z { w
w x y
o
s
z { w
w x y
x
x
q 
z { w
}
p
} q
s
z { w
p
x
|
o
p q
r
s t
s
~
} q
o u r
} q
o
q 
q 
€
y
s

‚
} q
y
In ML wird das Vorkommen von o in der
Abstraktion p q r s t o u r statisch gebunden
(vgl. 2.1.6, geschachtelte Prozeduren).
Zur Auswertung des Aufrufes von p benötigen
wir die Umgebung von der Definitionsstelle.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
124
Beachte:
Das Szenario kann viel komplexer sein. Die
Abstraktion könnte z.B. auch nicht-lokale Funktionen
benutzen:
ƒ
„ … †
‡ ˆ „
„ … †
‰
Š
‡ ˆ „
„ … †
‹
Ž
Œ 
Š
‡ ˆ „
„ … †
 

‡ ˆ „


Š
‰
Š ‘
 ’ ‰
 
“
Š ‘
Š
—
Œ 
Œ 
” Ž
‰ • – “
Œ 
‰
…  ˜
…  ˜
…  ˜
…  ˜
‡ ˆ „
™
Œ †
Š
š
›
Œ  †
Ebenso können die Let-Ausdrücke an beliebiger
Stelle im Rumpf einer Abstraktion vorkommen.
Lösungsansatz:
Das Ergebnis der Auswertung einer Abstraktion A in
einer Umgebung E ist das Tupel (A,E).
Dieses Tupel wir Abschluss der Funktion genannt
(engl. closure). Der Abschluss enthält die statisch
gebundenen Größen.
An der Aufrufstelle der Abstraktion liefert der Abschluss
dann die Umgebung, in der der Aufruf auszuwerten ist.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
125
Damit ergibt sich folgende informelle Semantik,
definiert über die Struktur der abstrakten Syntax:
Die Auswertung eval in Umgebung E
• eines Basiswerts w liefert w;
• einer Variablen v liefert den Wert von v in E;
• eines Konstruktorterms c [e1,...,en] liefert
c [eval e1 E, ... , eval en E ] ;
• einer Operation oprnd liefert das Ergebnis der
Anwendung von oprnd auf die Ergebnisse der
Teilausdrücke;
entsprechend für den bedingten Ausdruck;
• einer Abstraktion a liefert den Abschluss (a,E)
• einer Applikation App(e,ep):
- eval(e, E) ergibt Abschluss (Abs(x,e1),E1)
- eval(ep,E) ergibt vp
- damit ergibt sich: eval( e1, (x,vp)::E1 )
• eines Let-Ausdrucks: geeignete Erweiterung von E.
Realisierung des Interpreters in ML
Die Realisierung besteht aus drei Teilen:
• Beschreibung der abstrakten Syntax und der
Datentypen für die Werte
• Beschreibung der Auswertungsfunktion
• Beschreibung von Hilfsfunktionen
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
126
Umsetzen der abstrakten Syntax nach ML:
type ident
type oprnd
= string
= string
datatype expr
Int
of
| Bool
of
| Con
of
| Var
of
| UnOp
of
| BinOp of
| Cond
of
| Abs
of
| App
of
| Let
of
;
=
int
bool
ident
ident
oprnd
oprnd
expr
ident
expr
ident
* expr list
*
*
*
*
*
*
expr
expr * expr
expr * expr
expr
expr
expr * expr
Beschreibung der Werte, d.h. der Ausdrücke in
Normalform:
datatype value =
Intv of int
| Boolv of bool
| Conv of ident * value list
| Clos of (ident * expr) * env
withtype env = (ident*value) list
;
Da jedem Wert auch ein Ausdruck entspricht, ist eine
Trennung in Werte und Ausdrücke nicht nötig; sie
bietet allerdings mehr Typsicherheit.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
127
Beim Auslesen aus der Umgebung wird jeweils die
zuletzt eingetragene Bindung mit dem passenden
Bezeichner geliefert:
fun lkup x ((y,e)::E) =
if x=y then e else lkup x E ;
Programmierung der Auswertungsfunktion:
Umsetzung der informellen Auswertungssemantik in eine
rekursive Funktion:
fun eval (Int i)
E = Intv i
| eval (Bool b)
E = Boolv b
| eval (Con(c,l))
E =
Conv(c,map (fn x=>eval x E) l)
| eval (Var x)
E = lkup x E
| eval (UnOp (f,e))
E = apply1 f (eval e E)
| eval (BinOp (f,el,er))E =
apply2 f (eval el E,eval er E)
| eval (Cond (e,e1,e2)) E =
if (fn(Boolv b)=>b) (eval e E)
then eval e1 E
else eval e2 E
| eval (Abs (x,e))
E = Clos ((x,e),E)
| eval (App (e,ep))
E =
let val Clos ((w,eb),E1) = eval e E in
let val v = eval ep E in
eval eb ((w,v)::E1)
end end
| eval (Let (x,e,eb))
E =
eval eb ((x,eval e E)::E)
;
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
128
Es verbleibt die Anwendung der vordefinierten
Operationen:
œ  ž
Ÿ ¡ ¢ £
¤ ž ¥ ¦ ¤
§ ¨ ¥ ¥ ¡ ©
­
Ÿ ¡ ¢ £
¤ ® ¯ ¤
§ ° ¥ ž ©
­
Ÿ ¡ ¢ £
¤ ¦ ¡ ¤
­
Ÿ ¡ ¢ £
²
Ÿ ¡ ¢ ¼
¤ ½ ¤
§ ¾ ž ¦ ©
ª ³ ¾ ž ¦ ©
¢ «
¬
¾ ž ¦ ©
§ ª ½ ¢ «
­
Ÿ ¡ ¢ ¼
¤ ¿ ¤
§ ¾ ž ¦ ©
ª ³ ¾ ž ¦ ©
¢ «
¬
¾ ž ¦ ©
§ ª ¿ ¢ «
­
Ÿ ¡ ¢ ¼
¤ À ¤
§ ¾ ž ¦ ©
ª ³ ¾ ž ¦ ©
¢ «
¬
¾ ž ¦ ©
§ ª À ¢ «
­
Ÿ ¡ ¢ ¼
¤ Á ¤
§ ¾ ž ¦ ©
ª ³ ¾ ž ¦ ©
¢ «
¬
¨ ¥ ¥ ¡ ©
§ ª Á ¢ «
­
Ÿ ¡ ¢ ¼
¤ Â ¤
§ ¾ ž ¦ ©
ª ³ ¾ ž ¦ ©
¢ «
¬
¨ ¥ ¥ ¡ ©
§ ª Â ¢ «
­
Ÿ ¡ ¢ ¼
¤ ¬ ¤
§ ¾ ž ¦ ©
ª ³ ¾ ž ¦ ©
¢ «
¬
¨ ¥ ¥ ¡ ©
§ ª ¬ ¢ «
­
Ÿ ¡ ¢ ¼
¤ ¬ ¤
§ ¨ ¥ ¥ ¡ ©
¬
¨ ¥ ¥ ¡ ©
§ ª ¬ ¢ «
­
Ÿ ¡ ¢ ¼
¤ ¬ ¤
§ ° ¥ ž ©
œ  ž
­
Ÿ ž ¯
­
Ÿ ¡ ¢ ¼
²
¶
¬
¨ ¥ ¥ ¡ ©
§ ¤ ± ¥ ž ² ¤ ³ ´ ª ³¢ µ « «
¬
ª
§ ° ¥ ž ©
§ ¤ ± ¥ ž ² ¤ ³ ´ ª ³¢ µ « «
¬
¢
¶
§ · ¸ ¸ ¥ ¸
» ¡ ¢ £ «
¬
ª «
ª ³¨ ¥ ¥ ¡ ©
§ ± ³ ¡ « ³
¬
¨ ¥ ¥ ¡ ©
¬
§ · ¸ ¸ ¥ ¸
Ÿ ¡ ¡ · Ä
§ ´ µ ³ ´ µ «
¬
Ÿ ¡ ¡ · Ä
§ ª Å Å ¡ ³¢ Å Å ¡ Ã «
¬
§ œ ž
§ ¨ ¥ ¥ ¡ ©
² ¹
¢ «
° ¥ ž ©
§ ± ¬ ± Ã
² ¹
¸ Ÿ º ² ·
§ ž ¥ ¦
ª «
§ ± Ã ³ ¡ Ã « «
Ÿ ž ¯ Ÿ ¡ ² ¥
¸ Ÿ º ² ·
Ÿ ¡ ¡ · Ä
§ ¡ ³ ¡ Ã « «
» ¡ ¢ ¼ «
¦ ¸  ·
Æ « ¬ Á Æ «
§ Ÿ ¡ ¢ ¼
¤ ¬ ¤
§ ª ³¢ « «
Ÿ ž ¯ Ÿ ¡ ² ¥
Ÿ ¡ ¡ · Ä
§ ¡ ³ ¡ Ã «
¹
wobei die Definitionen der verwendeten Ausnahmen
wie folgt aussehen:
· ª ± · ¦ º ¥ ž
» ¡ ¢ £ ¹
· ª ± · ¦ º ¥ ž
» ¡ ¢ ¼ ¹
œ  ž
²
· ¸ ¸ ¥ ¸
14.12.2005
¬
¸ º ž ¦
§ ¤Ç ž È Ÿ ¦ ± ®
É ª ± · ¦ º ¥ ž Å
¤ Ê ² Ê ¤Ç ž ¤ « ¹
© A. Poetzsch-Heffter, TU Kaiserslautern
129
Beispiele: (Anwendung der eval-Funktion)
Sei a ein TinyML-Ausdruck, dann lässt er sich mit
(eval a E) auswerten; hier folgen ein paar Beispiele
für TinyML-Ausdrücke:
val succ
val plus
= Abs ("x", BinOp ("+",Var "x",Int 1));
= Abs ("x", Abs ("y",
BinOp ("+",Var "x",Var "y")));
val twice = Abs ("f", Abs ("x",
App (Var "f", App (Var "f",Var "x"))));
val comp = Abs ("f",Abs ("g", Abs ("x",
App (Var "f", App (Var "g",Var "x")))));
val fourt = App (twice, twice);
val let1 =
Let ("x",Int 7,
Let ("f",Abs ("y",BinOp ("+",Var "x",Var "y")),
App (Var "f",Int 5)
));
val l1 = Con ("cons",[Int 9,Con ("nil",[])]);
val ll = Con ("cons",[Int 2,
Con ("cons",[BinOp ("+",Int 5,Int 4),
Con ("cons",[Int 4,
Con ("nil",[])])])]);
Auswertungsbeispiele:
- val erg = eval (App (App (fourt, succ), Int 7)) [];
val erg = Intv 11 : value
- val c = eval (App (fourt, succ)) [];
val c = Clos (("x", App (#,#)),[("f",Clos #)]): value
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
130
Beispiele: (Zur Diskussion des Abschlusses)
Schließlich zwei Beispiele, die demonstrieren, warum
man bei der Auswertung von Abstraktionen einen
Abschluss bilden muss:
1. Einfacher let-Ausdruck:
Ë Ì Í
Î Ï Ë
Ð
Ñ
Ò
Ó Ô
Õ Ô
Ö
Ñ ×
Ð
Ø
Ö
Ì Ô Ù
Als abstrakter Syntaxbaum:
Ú Ì Í
Û Ü Ð Ü Ý Þ Ô Í
Ò Ý
ß à á
Û ÜÖ Ü Ýâ Ó Ô ã ä Û Ü Ø Ü Ýå Ï æ
Ü Ð Ü Ýå Ï æ
ÜÖ Ü ç ç ç
Auswertung liefert:
è Ë é á
Û
Û ÜÖ Ü Ý
ê
â Ó Ô ã ä
Û Ü Ð Ü Ý Þ Ô Í Î
Û Ü Ø Ü Ýå Ï æ
Ò ç
Ü Ð Ü Ýå Ï æ
ÜÖ Ü ç
ç Ý
ë
ç
2. Let-Ausdruck mit Funktionsabstraktionen:
let val s = (fn y => y+1)
in (fn f => fn x => f (f x)) s
end;
Auswertung liefert:
è Ë é á
Û
Û Ü Ð Ü Ý
ê
ß ä ä
Û Ü Õ Ü Ýè Ë é á
Û å Ï æ
ì ç Ý
Ü Õ Ü Ý
ì ç ç Ý
Û Ü á Ü Ýè Ë é á
ì ç
ë
ç
( Auswertung siehe nächste Seite )
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
131
Auswertung des 2. Ausdrucks, hier in λ-Notation:
let s = λ y.y+1 in λ f. λ x. f (f x) s end
Auswertung mit leerer Umgebung:
eval (let s = λ y.y+1 in λ f. λ x. f (f x) s end) [ ]
=
eval (λ f. λ x. f (f x) s) [ (s, eval λ y.y+1 [] ) ]
=
eval (λ f. λ x. f (f x) s) [ (s, Clos ( (y,y+1), [] ) ) ]
=
let Clos ( (w,eb), E1) =
eval (λ f. λ x. f (f x) ) [ (s, Clos ( (y,y+1), [] ) ) ]
in let v = eval s [ (s, Clos ( (y,y+1), [] ) ) ]
in eval eb ( (w,v)::E1) end end
=
let Clos ( (w,eb), E1) =
Clos ( (f, λ x. f (f x)) , [ (s, Clos ( (y,y+1), [] ) ) ]
in let v = Clos ( (y,y+1), [] ) )
in eval eb ( (w,v)::E1) end end
=
eval λ x. f (f x)
[ (f, Clos ( (y,y+1), [] ) ) , (s, Clos ( (y,y+1), [] ) ) ]
=
Clos ( ( x, f (f x) ),
[ (f, Clos ( (y,y+1), [] ) ) , (s, Clos ( (y,y+1), [] ) ) ]
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
132
Bemerkungen:
• Der Interpreter für TinyML demonstriert die
Verwendung von Funktionsabschlüssen, einer
zentralen Technik bei der Implementierung
funktionaler Sprachen.
• Nicht eingegangen wurde auf die Behandlung
rekursiver Funktionen. Dafür werden „zyklische“
Umgebungen benötigt: Die Umgebung zum
Abschluss einer rekursiven Funktion f muss
eine Bindung für f enthalten. (Die Realisierung
zyklischer Umgebungen in funktionalen Sprachen
bedarf zusätzlicher Techniken.)
2.3.3 Übersetzung einer einfachen
funktionalen Sprache
Die Übersetzungstechnik ist bei funktionalen Sprachen
je nach Klasse unterschiedlich:
strikte Sprachen
í
SECD-artige Maschinen
nicht-strikte Sprachen
í
Graphreduktion
Wir betrachten hier den ersten Fall.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
133
Vorgehen:
• Erläuterung der SECD-Maschine
• Realisierung eines Übersetzers: TinyML
î
SECD
Motivation:
• Kennen lernen einer abstrakten Maschine
• Beispiel zur Übersetzung funktionaler Sprachen
• Benutzung funktionaler Sprachen zur Übersetzung
Die SECD-Maschine:
Die SECD-Maschine ist eine typische abstrakte
Maschine:
• Sie abstrahiert von speziellen Eigenschaften realer
Maschinen und bietet damit eine gemeinsame
Plattform zum Erarbeiten vieler maschinenunabhängiger Übersetzungsvorgänge.
• Sie bietet eine Zwischensprache, die auf die Übersetzung funktionaler Sprachen zugeschnitten ist.
Damit ergeben sich zwei Übersetzungsschritte:
funktionale
Sprache
14.12.2005
1. Üb.schritt
SECD
-Code
2. Üb.schritt
© A. Poetzsch-Heffter, TU Kaiserslautern
Maschinen
-Sprache
134
1. Übersetzungsschritt:
• Sprachanalyse (kontextfrei, kontextabhängig)
• Übersetzung von Hochsprachkonstrukten
(Rekursion, Pattern Matching, komplexere
Ausdruckskonstrukte, Modulkonstrukte, ...)
• Namen werden durch Indizes ersetzt.
• Zielsprachunabhängige Optimierungen
2. Übersetzungsschritt:
• Übersetzung der zum Teil noch recht maschinenfernen Konstrukte der abstrakten Maschine
• Zielsprachabhängige Optimierungen
Wir betrachten hier nur den 1. Übersetzungsschritt.
Während reale Maschinen meist nur mit elementaren
Werten arbeiten, operiert die SECD-Maschine auf
vier Kellern, den sogenannten Registern:
• Stack: der Keller für Zwischenergebnisse
• Environment: enthält die Variablenbindungen
• Control: speichert den aktuell auszuführenden Code
• Dump: kellert Maschinenkonfigurationen
Zentrale Idee der SECD-Maschine:
Verzichte auf Sprünge und verwalte auch das
Ablaufverhalten kellerartig.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
135
Syntax der SECD-Maschinensprache:
Die SECD-Maschine besitzt die folgenden Befehle
(abstrakte Syntax beschrieben als ML-Datentyp):
datatype code =
LD
of value
| LDV of int
| LDC of code list
| LDT of string * int
| APP
| RAP of int
| DUM of int
| COND of code list * code list
| RET
| ADD|SUB|MULT|NOT|EQ|LT|GT|HD|TL
;
Dabei repräsentiert der Typ ï ð ñ ò ó die von der
SECD-Maschine unterstützten Werte:
datatype value =
I of int
| B of bool
| T of string * value list
| CL of code list * value list
Neben den Basiswerten ô õ ö und ÷ ø ø ñ kann die SECDMaschine direkt mit Termen (T) und mit FunktionsAbschlüssen (CL) umgehen, wobei:
• die 1. Komponente den Code für Abstraktion enthält,
• die 2. Komponente die Umgebung repräsentiert.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
136
Semantik der SECD-Maschinensprache:
Die Arbeitsweise der SECD-Maschine (und damit
die Semantik ihrer Befehle) beschreiben wir
operationell, indem wir den Zustandsraum,
Transitionsregeln sowie initiale und terminale
Zustände beschreiben:
• Ein Zustand ist ein Tupel der Form (S, E, C, D) mit
S : value list, E : value list, C : code list,
D : (value list * value list * code list) list
• Transitionsregeln haben die Form:
(S,E,C,D) ù (S‘,E‘,C‘,D‘)
und beschreiben den Übergang von einem
Zustand in den Folgezustand (siehe unten).
• In initialen Zuständen sind S, E, D leer; C enthält
den auszuführenden Code.
• Ein korrekt terminierendes Programm endet in
einem Zustand der Form (S, E, [ ], [ ] ).
Transitionsregeln:
Im Folgenden beschreiben wir die Transitionsregeln
der SECD-Maschine mit Ausnahme derjenigen für
RAP (rekursive Applikation) und DUM (Erzeugen von
Dummy-Einträgen in der Umgebung). Diese Befehle
dienen der hier nicht beschriebenen Behandlung
rekursiver Funktionen.
Keller werden mit der Listennotation beschrieben.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
137
Ladebefehle LD, LDV, LDC, LDT:
Alle Ladebefehle kellern ihr Argument.
Int- und bool-Werte werden mit LD geladen:
( S, E, (LD x)::C, D )
( x::S, E, C, D )
ú
Der Wert einer Variablen wird mit LDV n geladen,
wobei n der Index der Variablen in der Umgebung ist
und die Funktion sel den n-ten Eintrag aus der
Umgebung ausliest:
( S, E, (LDV n)::C, D )
( (sel n E)::S, E, C, D )
ú
Der LDC-Befehl lädt den Code zu einer Funktion
in Form eines Funktionsabschlusses:
( S, E, (LDC C‘)::C, D )
ú
(CL (C‘,E)::S, E, C, D )
Der Befehl LDT (c,n) wendet den n-stelligen Konstruktor auf die n obersten Kellerelemente an:
( xn::...::x1::S, E, (LDT (c,n))::C, D )
ú
14.12.2005
( c(x1,...,xn)::S, E, C, D )
© A. Poetzsch-Heffter, TU Kaiserslautern
138
Die Befehle APP, COND und RET:
Der APP-Befehl erwartet einen Abschluss und das
Argument auf dem Stack. Er sichert die nachfolgende
Maschinenkonfiguration, fügt das Argument der Umgebung zu und wertet den Code des Abschlusses aus:
( (CL (C‘,E‘))::x::S, E, APP::C, D )
û
( [ ], x::E‘, C‘, (S,E,C)::D )
APP entspricht dem Ansprung einer Prozedur.
Ist der Control-Keller leer, erfolgt der „Rücksprung“, bei
dem die im Dump gesicherte Maschinenkonfiguration
wieder hergestellt wird; dabei bleibt das Ergebnis auf
dem Keller:
( x::S, E, [ ], (S‘,E‘,C‘)::D )
û
( x::S‘, E‘, C‘, D )
Der COND-Befehl realisiert einen bedingten Ausdruck.
Er erwartet einen booleschen Wert b auf dem Keller.
In Abhängigkeit von b wird einer der Zweige ausgeführt, die beide mit dem RET-Befehl schließen.
Die nachfolgende Codeliste wird gesichert:
( (B true)::S, E, (COND (CT,CF))::C, D )
û
(S, E, CT, ( [], [], C)::D )
( (B false)::S, E, (COND (CT,CF))::C, D )
û
14.12.2005
(S, E, CF, ( [], [], C)::D )
© A. Poetzsch-Heffter, TU Kaiserslautern
139
Der RET-Befehl bewirkt den „Rücksprung“ aus einem
Zweig eines bedingten Ausdrucks (er markiert den
Unterschied zu einem Rücksprung von einer Funktion):
( S, E, RET::[], ( [], [], C )::D )
( S, E, C, D )
ü
Befehle für die vordefinierten Operationen:
Die Wirkung der vordefinierten Operationen wird hier
anhand von Beispielen erläutert. Zu beachten ist, dass
die Argumente in umgekehrter Reihenfolge erwartet
werden:
( y::x::S, E, SUB::C, D )
ü
( (x-y)::S, E, C, D )
( y::x::S, E, EQ::C, D )
ü
( (x=y)::S, E, C, D )
( (cons (x,y))::S, E, HD::C, D )
ü
( x::S, E, C, D )
( (cons (x,y))::S, E, TL::C, D )
ü
( y::S, E, C, D )
Befehlszyklus der SECD-Maschine:
Ausgehend von einem initialen Zustand führe solange
Transitionsregeln aus, bis
• Control- und Dump-Keller leer sind oder
• ansonsten keine Regel mehr anwendbar ist.
Der zweite Fall kommt bei korrektem SECD-Code
nicht vor.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
140
Übersetzung von TinyML nach SECD:
Die folgende Übersetzungsfunktion ý þ ÿ ÿ übersetzt korrekte abstrakte TinyML-Syntaxbäume
in SECD-Code:
comtiml: expr * ident list
code list
Eine der Übersetzungsaufgaben ist es, die
Adressierung der Variablen bzgl. der Umgebung zu
realisieren (vgl. Erklärung zu LDV).
Die Zuordnung von Variablen zu Positionsindizes
wird in einer Bezeichnerliste verwaltet, die als
zweiter Parameter mitgeführt wird. Der Position einer
Variablen v in dieser Liste entspricht die Position
des Wertes von v in der Umgebung zur Laufzeit:
position: ident ident list int
fun position x (y::ys) =
if x=y then 1 else 1 + position x ys
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
141
Realisierung von comtiml Die Übersetzungsfuntion ist rekursiv über den
Aufbau der abstrakten Syntax von TinyML (vgl.
Folie 127) definiert (d.h. wir haben 10 Fälle zu
betrachten).
Grundlegendes Übersetzungsschema:
Für jeden TinyML-Ausdruck A wird der SECD-Code
erzeugt, der A auswertet und den resultierenden
Wert auf S kellert.
Übersetzung von Konstanten:
Direkt in den entsprechenden Ladebefehl
comtiml (Int i)
N = [ LD (I i) ]
comtiml (Bool b) N = [ LD (B b) ]
Übersetzung von Variablen:
Die Position der Variablen v in der Bezeichnerliste
wird zur Adressierung von v in der Umgebung N
benutzt:
comtiml (Var id) N =
[ LDV (position id N) ]
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
142
Übersetzung von Konstruktortermen:
1. Erzeuge Code zum Auswerten aller Subterme.
2. Hänge die resultierenden Codelisten zusammen.
3. Hänge einen LDT-Befehl mit der Argumentanzahl an:
comtiml (Con (id,expl)) N =
concat(map (fn e=>comtiml e N) expl)
@ [ LDT (id,length expl) ]
Übersetzung von vordefinierten Operationen:
Erzeuge Code für die Argumente und hänge dann
einen Maschinenbefehl an, der die vordefinierte
Operation realisiert. Der Zusammenhang zwischen
den Bezeichnern vordefinierter Operationen und
den zugehörigen Befehlen liefert folgende Funktion:
fun cmd "+" =
| cmd "hd" =
| cmd "eq" =
|
...
ADD
HD
EQ
Damit
comtiml (UnOp (f,e)) N = comtiml e N @ [cmd f]
comtiml (BinOp (f,e1,e2)) N =
comtiml e1 N @ comtiml e2 N @ [cmd f]
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
143
Beachte:
Bei den vordefinierten Operationen und den
Konstruktortermen landen die Argumente in
umgekehrter Reihenfolge auf dem Keller.
Übersetzung der Fallunterscheidung:
Erzeuge Code zum Auswerten der Bedingung und
dann den COND-Befehl mit den durch RET-Befehle
beendeten Zweigen:
comtiml (Cond (e,e1,e2)) N =
comtiml e N @
[COND ( comtiml e1 N @ [RET] ,
comtiml e2 N @ [RET] )]
Übersetzung der Funktionsanwendung:
Bei typkorrekten Programmen kann man davon ausgehen, dass das erste Argument zu einem Abschluss
ausgewertet wird. Der APP-Befehl erwartet den
Abschluss oben auf dem Laufzeitkeller, das Argument
darunter:
comtiml (App (eclos,earg)) N =
comtiml earg N @ comtiml eclos N @[APP]
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
144
Übersetzung der Funktionsabstraktion:
Der LDC-Befehl ist der Kern der Übersetzung
einer Abstraktion Abs (x,e) und bekommt als
Parameter den Code für den Rumpf e.
Dabei wird der Rumpf mit der um x erweiterten
Bezeichnerliste übersetzt:
comtiml (Abs(x,e)) N =
[LDC (comtiml e (x::N))]
Übersetzung von Let-Ausdrücken:
Operationell (nicht bzgl. der Typisierung) gilt
folgende Äquivalenz:
let x = e1 in e2 end ==
(λ x. e2) e1
Dementsprechend kann man den let-Ausdruck
wie den Ausdruck auf der rechten Seite übersetzen:
comtiml (Let(x,e1,e2)) N =
comtiml e1 N @
[LDC (comtiml e2 (x::N)), APP]
Damit sind alle Alternativen der abstrakten Syntax von
TinyML behandelt und die Übersetzungsfunktion
vollständig beschrieben.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
145
Bemerkungen:
• Die Übersetzungsfunktion demonstriert das
grundlegende Konzept zur Übersetzung funktionaler
Sprachen in abstrakten Maschinencode.
• Die Übersetzung ließe sich natürlich auch mittels
einer Attributierung spezifizieren. Wir betrachten hier
exemplarisch den bedingten Ausdruck:
comtiml (Cond (e,e1,e2)) N =
comtiml e N @
[COND ( comtiml e1 N @ [RET] ,
comtiml e2 N @ [RET] )]
Bezeichnerliste vom Typ ident list
Codeliste vom Typ code list
Cond
CB @
[ COND (CT @ [ RET ],
CE @ [ RET ] ) ]
expr
expr
CB
expr
CT
CE
Beachte, dass die rekursiven Aufrufe in der
Attributierungsspezifikation implizit sind.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
146
• Anhand der Übersetzung von TinyML sollte
illustriert werden, wie funktionale Sprachen zur
Beschreibung und Implementierung von Übersetzern
verwendet werden können.
• Das Beispiel ist nicht geeignet, die Benutzung der
SECD-Maschine zu motivieren; dafür müsste die
Quellsprache umfangreicher sein.
• Auf die Behandlung eines der zentralen Sprachmittel
(rekursive Funktionen) wurde nicht eingegangen.
Quellenhinweis:
Abschnitte 2.3.2 und 2.3.3 folgen weitgehend:
Martin Erwig: Grundlagen funktionaler Programmierung,
Oldenbourg Verlag; Kapitel 6.
14.12.2005
© A. Poetzsch-Heffter, TU Kaiserslautern
147
Herunterladen