Zur Semantik funktionaler Programme Zur Semantik funktionaler

Werbung
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
3. Funktionales Programmieren
Zur Semantik funktionaler Programme
3.4 Semantik, Testen und Verifikation
Zur Semantik funktionaler Programme (2)
In erster Näherung definiert eine Funktionsdeklaration eine partielle
Funktion. Gründe für Partialität:
1. Der Ausdruck, der die Funktion definiert, ist bereits partiell:
division dd dr
hd x:xs = x
Lernziele in diesem Unterabschnitt:
• Was bedeutet Auswertungssemantik?
=
dd `div` dr
2. Behandlung rekursiver Deklarationen:
• Wie sieht sie im Falle von Haskell aus?
a. Insgesamt unbestimmt:
• Welche Bedeutung haben Bezeichnerumgebungen dabei?
f :: a -> a
f x = f x
b. Teilweise unbestimmt (hier für negative Zahlen):
fac :: Integer -> Integer
fac n = if n==0 then 1 else n * fac(n -1)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
461
3.4 Semantik, Testen und Verifikation
TU Kaiserslautern
3. Funktionales Programmieren
Ziel:
462
3.4 Semantik, Testen und Verifikation
Begriffsklärung: (denotationelle Semantik)
Ordne jeder syntaktisch korrekten Funktionsdeklaration eine partielle
Funktion zu. Die Semantik beschreibt diese Zuordnung.
Eine Semantik, die jeder Funktionsdeklaration explizit eine partielle
Funktion als Bedeutung zuordnet, d.h. eine Abbildung von
Funktionsdeklarationen auf partielle Funktionen definiert, nennen wir
denotationell.
Wir unterscheiden hier denotationelle und operationelle Semantik.
Statt operationeller Semantik spricht man häufig von
Auswertungssemantik.
©Arnd Poetzsch-Heffter
©Arnd Poetzsch-Heffter
TU Kaiserslautern
463
©Arnd Poetzsch-Heffter
TU Kaiserslautern
464
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
Beispiel: (denotationelle Semantik)
Beispiel: (denotationelle Semantik) (2)
Eine denotationelle Semantik würde der obigen Funktionsdeklaration
von fac eine Funktion f
f : Z⊥ → Z⊥
Zwei mögliche Lösungen f1 und f2 :
(
f1 (k ) =
zuordnen, wobei
⊥ , falls k =⊥ oder k < 0
k ! , sonst


⊥ , falls k =⊥



0
,k < 0
f2 (k ) = 


 k ! , sonst
Z⊥ = { x | x ist Wert vom Typ Integer } ∪ {⊥}
Diese Funktion muss die Gleichung für fac erfüllen.
Das Symbol ⊥ steht dabei für ündefiniertünd wird häufig als bottom
bezeichnet.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
465
©Arnd Poetzsch-Heffter
3.4 Semantik, Testen und Verifikation
TU Kaiserslautern
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
Beispiel: (denotationelle Semantik) (3)
Beispiel: (denotationelle Semantik) (4)
Wir zeigen, dass f2 eine Lösung der Gleichung ist:
n =⊥: links:
f2 (⊥) =⊥
rechts: if ⊥= 0 then 1 else ⊥ ∗f2 (⊥ −1) = ⊥
Die denotationelle Semantik muss sicherstellen,
n < 0:
links:
rechts:
f2 (n) = 0
if n = 0 then 1 else n ∗ f2 (n − 1) = n ∗ 0 = 0
n = 0:
links:
rechts:
f2 (0) = 0! = 1
if 0 = 0 then 1 else 0 ∗ f2 (0 − 1) = 1
n > 0:
links:
rechts:
• dass es für jede Funktionsdeklaration mindestens eine Lösung
gibt, und
• eine Lösung auszeichnen, wenn es mehrere gibt.
In den meisten Programmiersprachen wählt man die Lösung, die an
den wenigsten Stellen definiert ist, und betrachtet nur so genannte
strikte Funktionen als Lösung:
f2 (n) = n!
if n = 0 then 1 else n ∗ f2 (n − 1)
= n ∗ (n − 1)! = n!
Genauso lässt sich zeigen, dass f1 eine Lösung ist.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
466
467
©Arnd Poetzsch-Heffter
TU Kaiserslautern
468
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
3. Funktionales Programmieren
Begriffsklärung: (strikte Funktionen)
3.4 Semantik, Testen und Verifikation
Bemerkungen:
Eine n-stellige Funktion oder Operation heißt strikt, wenn sie ⊥ als
Ergebnis liefert, sobald eines der Argumente ⊥ ist.
• Denotationelle Semantik basiert auf einer Theorie partieller
Beispiele: (nicht-strikte Funktionen)
strikter Funktionen und Fixpunkttheorie.
• Die dreistellige “Funktion” if-then-else und die boolschen
Operatoren && und || sind in fast allen Programmiersprachen
I
I
nicht strikt.
• In Haskell deklarierte Funktionen sind im Allg. nicht strikt:
• ⊥ steht für undefiniert, unabhängig davon, welcher der Gründe für
Partialität vorliegt.
ite :: Bool -> a -> a -> a
ite b x y = if b then x else y
Prelude >
45
©Arnd Poetzsch-Heffter
Vorteil: Für Beweise besser geeignet.
Nachteil: Theoretisch aufwendiger zu handhaben.
ite False (4 `div` 0) 45
TU Kaiserslautern
3. Funktionales Programmieren
469
©Arnd Poetzsch-Heffter
3.4 Semantik, Testen und Verifikation
TU Kaiserslautern
3. Funktionales Programmieren
Begriffsklärung: (operationelle Semantik)
470
3.4 Semantik, Testen und Verifikation
Begriffsklärung: (formaler/aktueller Parameter)
Eine Semantik, die erklärt, wie eine Funktion oder ein Programm
auszuwerten ist, nennen wir operationell oder
Auswertungssemantik .
Ein Bezeichner, der in einer Funktionsdeklaration einen Parameter
bezeichnet, wird formaler Parameter genannt.
Wir erläutern
Der Ausdruck oder Wert, der einer Funktion bei einer Anwendung
übergeben wird, wird aktueller Parameter genannt.
• eine Auswertungsstrategie für funktionale Programme,
• welche Rolle Bezeichnerumgebungen dabei spielen, und
• führen wichtige Begriffe ein.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
471
©Arnd Poetzsch-Heffter
TU Kaiserslautern
472
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
3. Funktionales Programmieren
Begriffsklärung: (Auswertungsstrategie)
3.4 Semantik, Testen und Verifikation
Beispiele: (Parameterübergabeverfahren)
Parameterübergabe:
1. Call-by-Value:
I
Die Auswertungsstrategie legt fest,
I
• in welchen Schritten die Ausdrücke ausgewertet werden und
I
• wie die Parameterübergabe geregelt ist.
Werte die aktuellen Parameter aus.
Benutze die Ergebnisse anstelle der formalen Parameter im
definierenden Ausdruck/Rumpf.
Werte den Rumpf aus.
2. Call-by-Name:
I
I
Ersetze alle Vorkommen der formalen Parameter durch die
(unausgewerteten) aktuellen Parameterausdrücke.
Werte den Rumpf aus.
Unterschiedliche Auswertungsstrategien führen im Allg. zu
unterschiedlichen Ergebnissen.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
473
©Arnd Poetzsch-Heffter
3.4 Semantik, Testen und Verifikation
TU Kaiserslautern
3. Funktionales Programmieren
474
3.4 Semantik, Testen und Verifikation
Beispiel: (Auswertungsstrategien
Beispiel: (Auswertungsstrategien (2)
Betrachte:
=
f (x,y) =
if x==0 then 1 else f (x-1,f(x-y,y))
=
Werte den Ausdruck f (1,0) aus:
=
1. Call-by-Value:
=
f (1 ,0)
=
=
=
if 1==0
then 1
if False then 1
else
else
=
f(1-1,f(1 -0 ,0))
=
f(1-1,f(1 -0 ,0))
TU Kaiserslautern
f (0, f(1 ,0) )
f (0, if 1==0
then 1
else
f(1-1,f(1 -0 ,0)))
....
f (0, f(0, f (1 ,0) ))
....
Diese Auswertung kommt nicht zum Ende, d.h. sie terminiert nicht.
f (1-1, f(1 -0 ,0) )
©Arnd Poetzsch-Heffter
f (0, f(1 -0 ,0) )
475
©Arnd Poetzsch-Heffter
TU Kaiserslautern
476
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
3. Funktionales Programmieren
Beispiel: (Auswertungsstrategien (3)
Beispiel: (Auswertungsstrategien (4)
2. Call-by-Name:
=
=
=
=
=
f (1 ,0)
if 1==0
then 1 else f(1-1,f(1 -0 ,0))
=
if False then 1 else f(1-1,f(1 -0 ,0))
=
f( 1-1, f(1 -0 ,0) )
if 1-1==0 then
else
©Arnd Poetzsch-Heffter
1
f(1-1-1,f(1-1-f(1-0, 0),f(1 -0 ,0)))
TU Kaiserslautern
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
if 1-1==0
then
else
1
f(1-1-1,f(1-1-f(1-0, 0),f(1 -0 ,0)))
if True
then
else
1
f(1-1 -1,f(1-1-f(1 -0 ,0) ,f(1 -0 ,0)))
1
Mit Call-by-Name terminiert die Auswertung von f(1,0).
477
©Arnd Poetzsch-Heffter
3.4 Semantik, Testen und Verifikation
TU Kaiserslautern
3. Funktionales Programmieren
Begriffsklärung: (Normalform)
478
3.4 Semantik, Testen und Verifikation
Informelle Auswertungssemantik von Haskell
Haskell benutzt Call-by-Need zur Parameterübergabe und
Auswertung.
Call-by-Need ist eine verfeinerte Form von Call-by-Value, bei der ein
aktueller Parameter, wenn er mehrfach benötigt wird, nur einmal
ausgewertet wird.
Der Ergebnisausdruck einer terminierenden Auswertung wird
Normalform genannt.
In einer Sprache ohne Seiteneffekte wie Haskell unterscheiden sich
Call-by-Need und Call-by-Value aber nicht im Ergebnis, sondern nur in
der Effizienz der Auswertung.
Die Ausdrücke werden von
• von links nach rechts (engl. leftmost),
• von außen nach innen (engl. outermost) und
• nur, wenn sie gebraucht werden,
ausgewertet.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
479
©Arnd Poetzsch-Heffter
TU Kaiserslautern
480
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
3. Funktionales Programmieren
Eine Teilsprache von Haskell
3.4 Semantik, Testen und Verifikation
Beispielprogramme
data Exp =
Cond
Exp Exp Exp
| Ident
String
| Binary Op Exp Exp
| Lambda String Exp
| Appl
Exp Exp
| Let
String Exp Exp
-- let a = 5
-- in let b = a + 7
-in let a = 0 in b
letx = Let "a" ( IConst 5)
(Let "b" ( Binary Plus (Ident "a") ( IConst 7))
(Let "a" ( IConst 0) (Ident "b")))
| BConst Bool
| IConst Integer
| Closure String Exp Env
deriving (Eq , Show)
data Op = Plus | Mult | Eq
deriving (Eq , Show)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
481
©Arnd Poetzsch-Heffter
3.4 Semantik, Testen und Verifikation
TU Kaiserslautern
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
Beispielprogramme (2)
Beispielprogramme (3)
-- let fac = \n -> if n==0 then 1 else n * fac(n+( -1))
-- in fac 10
facx =
Let "fac"
( Lambda "n"
(Cond ( Binary Eq ( Ident "n") ( IConst 0))
( IConst 1)
( Binary Mult ( Ident "n")
(Appl ( Ident "fac")
( Binary Plus ( Ident "n")( IConst (-1)
))
)
)
) )
(Appl ( Ident "fac") ( IConst 10))
-- let o = \f -> \g -> \x -> f (g x) in
-- in let fac = \n->if n==0 then 1 else n*fac(n+( -1))
-in (fac `o` fac) 5
compx =
Let "o" ( Lambda "f"
( Lambda "g"
( Lambda "x" (Appl (Ident "f") (Appl (
Ident "g") (Ident "x"))))))
(Let "fac" ... -- wie oben
(Appl (Appl (Appl (Ident "o") (Ident "fac")) (
Ident "fac")) ( IConst 5))
)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
483
©Arnd Poetzsch-Heffter
482
TU Kaiserslautern
484
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
3. Funktionales Programmieren
Auswertungsbeispiel
=
=
=
=
=
=
=
Datentyp für Bezeicherumgebung
eval (let a=5 in let b=a+7 in let a=0 in b)
eval (let b=a+7 in let a=0 in b)
[]
data Env = Ec [ (String ,(Exp ,Env)) ]
[ a=(5 ,[]) ]
[ a=(0 ,[.. .]) , b=(a+7 ,[a=(5 ,[]) ]), a=(5 ,[]) ]
eval (a+7)
(eval a
insert :: String -> (Exp ,Env) -> Env -> Env
-- ( insert bez xe e) traegt die Bindung (bez ,xe)
-- in die Umgebung e ein
[ a=(5 ,[]) ]
[ a=(5 ,[]) ]) + (eval 7
[ a=(5 ,[]) ])
lookUp :: String -> Env -> (Exp ,Env)
-- ( lookUp bez e) liefert das Paar xe der ersten
-- gefundenen Bindung (bez ,xe) mit Bezeichner bez
(eval 5 []) + 7
5 + 7 =
deriving (Eq , Show)
emptyEnv :: Env
-- leere Bezeichnerumbegung
eval (let a=0 in b) [ b=(a+7 ,[a=(5 ,[]) ]), a=(5 ,[]) ]
eval b
3.4 Semantik, Testen und Verifikation
12
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
485
©Arnd Poetzsch-Heffter
3.4 Semantik, Testen und Verifikation
TU Kaiserslautern
3. Funktionales Programmieren
Funktionsabschlüsse:
486
3.4 Semantik, Testen und Verifikation
Funktionsabschlüsse: (2)
Vorgehen:
Das Ergebnis eines funktionswertigen Ausdrucks wird als Triple
Fragen:
Wie wird das Ergebnis eines funktionswertigen Ausdrucks dargestellt?
Wie wird eine benutzerdeklarierte Funktion in der
Bezeichnerumgebung dargestellt?
Closure s r e
dargestellt, den sogenannten Funktionsabschluss (engl. Closure):
• s bezeichnet den formalen Parameter
• r bezeichnet den Funktionsrumpf
• e bezeichnet die aktuell gültige Umgebung.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
487
©Arnd Poetzsch-Heffter
TU Kaiserslautern
488
3. Funktionales Programmieren
3.4 Semantik, Testen und Verifikation
3. Funktionales Programmieren
Beispiel:
=
=
=
=
=
=
3.4 Semantik, Testen und Verifikation
Auswertungssemantik für die Haskell-Teilsprache:
eval (let a = 6 in let ida = \x -> (x+a) in ida 9)
eval (let ida = \x -> (x+a) in ida 9)
eval (ida 9)
[]
[ a=(6 ,[]) ]
[ ida = (\x->(x+a) ,[a=(6 ,[]) ]),a=(6 ,[])]
wende (eval ida [ida=(\x->(x+a) ,[a=(6 ,[]) ]),a=(6 ,[]) ])
auf 9 mit e = [ida=(\x->(x+a) ,[a=(6 ,[]) ]) , a=(6 ,[])] an
wende
(eval (\x -> (x+a)) [a=(6 ,[]) ])
auf 9 mit e an
wende
( Closure x (x+a) [a=(6 ,[])] )
auf 9 mit e an
eval (x+a) [ x=(9,e), a=(6 ,[]) ]
=
(eval x [x=(9,e),a=(6 ,[]) ]) + (eval a [x=(9,e),a=(6 ,[]) ])
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
489
eval :: Exp -> Env -> Exp
eval (Cond
bx tx ex) e = let BConst b = eval bx e
in if b then eval tx e
else eval ex e
eval (Ident s
) e = let (xv ,ev) = ( lookUp s e)
in eval xv ev
eval ( Binary bo lx rx) e = let IConst li = eval lx e
IConst ri = eval rx e
in evalOp bo li ri
eval ( Lambda s bx
) e = Closure s bx e
eval (Appl
fx px
) e = let Closure s b ce = eval fx e
in eval b ( insert s (px ,e) ce)
eval (Let
s dx bx ) e = let en = ( insert s (dx ,en) e)
in eval bx en
eval x
e = x
©Arnd Poetzsch-Heffter
3.4 Semantik, Testen und Verifikation
TU Kaiserslautern
3. Funktionales Programmieren
Auswertungssemantik für die Haskell-Teilsprache: (2)
490
3.4 Semantik, Testen und Verifikation
Bemerkungen:
• Wir haben die Auswertungssemantik von Haskells Teilsprache in
Haskell selbst definiert, weil wir keine andere
Beschreibungstechnik kennen.
evalOp :: Op -> Integer -> Integer -> Exp
evalOp Plus li ri = IConst (li+ri)
evalOp Mult li ri = IConst (li*ri)
evalOp Eq
li ri = BConst (li==ri)
• Üblicherweise wird man einen anderen
Beschreibungsformalismus wählen.
• Mit Ausnahme der Gleichung für die rekursiven Definitionen von
Let-Ausdrücken lassen sich alle Gleichungen als
Ersetzungsregeln benutzen.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
491
©Arnd Poetzsch-Heffter
TU Kaiserslautern
492
Herunterladen