diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2015-11-11 20:20:22 +0100 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2015-11-11 20:20:22 +0100 |
commit | 3089deff57382181fe76ff45e14d44c299f72b2e (patch) | |
tree | 0fe4fd8fbc5fe3acd18890f39a71a4b6d9618c9f /ws2015/FFP/blaetter | |
parent | abb5d050e3c4175dde65a96e5f8cf7c822d5e2ec (diff) | |
download | uni-3089deff57382181fe76ff45e14d44c299f72b2e.tar uni-3089deff57382181fe76ff45e14d44c299f72b2e.tar.gz uni-3089deff57382181fe76ff45e14d44c299f72b2e.tar.bz2 uni-3089deff57382181fe76ff45e14d44c299f72b2e.tar.xz uni-3089deff57382181fe76ff45e14d44c299f72b2e.zip |
Downloaded questions for FFP-04
Diffstat (limited to 'ws2015/FFP/blaetter')
-rw-r--r-- | ws2015/FFP/blaetter/04/FFP_U04_Lazy.hs | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/ws2015/FFP/blaetter/04/FFP_U04_Lazy.hs b/ws2015/FFP/blaetter/04/FFP_U04_Lazy.hs new file mode 100644 index 0000000..f82b54c --- /dev/null +++ b/ws2015/FFP/blaetter/04/FFP_U04_Lazy.hs | |||
@@ -0,0 +1,380 @@ | |||
1 | -- Fortgeschrittene Funktionale Programmierung, | ||
2 | -- LMU, TCS, Wintersemester 2015/16 | ||
3 | -- Steffen Jost, Alexander Isenko | ||
4 | -- | ||
5 | -- Übungsblatt 04. 11.11.2015 | ||
6 | -- | ||
7 | -- Thema: | ||
8 | -- | ||
9 | -- Anweisung: | ||
10 | -- Gehen Sie diese Datei durch und bearbeiten Sie | ||
11 | -- alle Vorkommen von undefined bzw. die mit -- !!! TODO !!! | ||
12 | -- markierten Stellen. Testen Sie Ihre Lösungen mit GHCi! | ||
13 | -- | ||
14 | -- | ||
15 | |||
16 | import Data.Map as Map | ||
17 | import Data.Set as Set | ||
18 | |||
19 | |||
20 | |||
21 | ---- A4-1 Verzögerte Auswertung | ||
22 | -- Gegeben ist folgendes Programm: | ||
23 | xs = [1..] | ||
24 | foo x = 2 * x | ||
25 | ys = foo <$> xs | ||
26 | rs = take 1 $ drop 1 ys | ||
27 | |||
28 | {-Skizzieren Sie mit Papier und Bleistift, | ||
29 | wie am Ende der Speicher aussieht, wenn lediglich | ||
30 | rs | ||
31 | ausgewertet wurde (z.B. durch Anzeige am Bildschirm). | ||
32 | |||
33 | Welche Strukturen gibt es im Speicher? | ||
34 | Wie viele Thunks befinden sich noch im Speicher? | ||
35 | Auf welche Adressen zeigen xs, ys, rs in den von Ihnen skizzierten Speicher? | ||
36 | |||
37 | Hinweise: | ||
38 | - An jeder Speicheraddresse sollte sich entweder ein Thunk (also ein Programmausdruck) | ||
39 | oder ein Wert befinden. | ||
40 | - Für Datentypen wird der Wert dargestellt als Tupel aus Konstruktor | ||
41 | und den Speicheraddressen seiner Argumente. | ||
42 | - Funktionsdefinitonen lassen wir zur Vereinfachung aus dem Speicher heraus. | ||
43 | - Speicheraddressen dürfen Sie völlig willkürlich wählen | ||
44 | |||
45 | BEISPIEL: | ||
46 | |||
47 | -- Programm: | ||
48 | zs = [27..] | ||
49 | ts = take 1 $ drop 2 zs | ||
50 | x = head ts | ||
51 | |||
52 | -- Nach Auswertung von x haben wir den Zustand: | ||
53 | |||
54 | [ <01>,(:),<11>,<02> | <02>,(:),<12>,<03> | <03>,(:),<13>,<04> | <04>,"[30..]" | ||
55 | | <11>,Int,27 | <12>,Int,27+1 | <13>,Int,29 | ||
56 | | <21>,(:),<13>,<22> | <22>,[] | ||
57 | ] | ||
58 | |||
59 | Thunks: <04>,<12> | ||
60 | |||
61 | zs -> <01> | ||
62 | ts -> <21> | ||
63 | x -> <13> | ||
64 | -} | ||
65 | |||
66 | |||
67 | |||
68 | |||
69 | ---- A4-2 Zirkularität | ||
70 | -- a) | ||
71 | -- Schreiben Sie ein zirkuläres Programm transcl, | ||
72 | -- welches zu einer gegebenen Relation r :: a -> [a] | ||
73 | -- und einer als Liste gegebenen Menge, | ||
74 | -- die transitive Hülle dieser Menge zu der Relation berechnet. | ||
75 | -- | ||
76 | -- Eine Relation r :: a -> [a] ist dabei so kodiert, | ||
77 | -- das r x die Menge aller Elemente ist, welche zu x in Relation stehen. | ||
78 | -- | ||
79 | -- HINWEIS: | ||
80 | -- Das Ergebnis soll eine Menge modellieren, es darf also kein Element | ||
81 | -- doppelt vorkommen. Die Reigenfolge der Elemente in der Liste ist aber egal. | ||
82 | -- | ||
83 | -- BEISPIELE: | ||
84 | -- | ||
85 | -- > transCl rel1 [22] | ||
86 | -- [33,44] | ||
87 | -- > transCl rel1 [2,5] | ||
88 | -- [2,5,4,6,8,1,3,7,9] | ||
89 | -- | ||
90 | -- > sort $ transCl rel2 [42,8,9] | ||
91 | -- [1,2,4,5,7,8,9,10,11,13,14,16,17,20,21,22,26,28,32,34,40,42,52,64] | ||
92 | -- | ||
93 | -- HINWEIS: Folgen Sie dem nub2 Beispiel aus Folie 3-30 | ||
94 | |||
95 | |||
96 | transCl :: (Eq a) => (a -> [a]) -> [a] -> [a] | ||
97 | transCl r xs = undefined -- !!! TODO !!! | ||
98 | |||
99 | -- Zum Testen: | ||
100 | rel1 11 = [22] | ||
101 | rel1 22 = [33] | ||
102 | rel1 33 = [44] | ||
103 | rel1 n | ||
104 | | even n, n>=1, n<=9 = [2,4,6,8] | ||
105 | | odd n, n>=1, n<=9 = [1,3,5,7,9] | ||
106 | | otherwise = [n] | ||
107 | |||
108 | rel2 n | ||
109 | | even n = [n,n `div` 2] | ||
110 | | otherwise = [3*n+1,n] | ||
111 | |||
112 | |||
113 | -- b) | ||
114 | -- Implementieren Sie die Aufgabe noch mal ganz schnell | ||
115 | -- ohne Rücksicht auf Zirkularität oder Effizienz, | ||
116 | -- sondern ganz bequem mit der Standardbibliothek für Data.Set | ||
117 | |||
118 | transClS :: (Ord a) => (a -> Set a) -> Set a -> Set a | ||
119 | transClS rel set = undefined -- !!! TODO !!! | ||
120 | |||
121 | |||
122 | |||
123 | |||
124 | ---- A4-3 Verzögerte Auswertung | ||
125 | {-Ein Kollege von Dr Jost meinte einmal, dass man Dinge am Besten durch Implementation erlernt. | ||
126 | Also wollen wir in dieser Aufgabe verzögerte Auswertung für einen Fragment von Haskell Implementieren. | ||
127 | Wir machen uns das Leben einfach und nehemen nur folgendes, minimales Fragment her: | ||
128 | * Variablen z.B. "y" | ||
129 | * Anonyme Funktionen z.B. "\x->x" | ||
130 | * Funktionsanwendung z.B. "(\x->x) y" evaluiert zu "y" | ||
131 | Wir verzichten also auf sehr viel: keine Pattern-Matching, kein if-then-else,... | ||
132 | ...sogar auf Basisdatentypen wie Int oder Bool verzichten wir! | ||
133 | |||
134 | Die Terme unseres Sprach-Fragments modellieren wir durch folgenden Datentyp: | ||
135 | -} | ||
136 | |||
137 | data Term = Var Variable | Abs Variable Term | App Term Term | ||
138 | deriving (Eq) | ||
139 | |||
140 | type Variable = String | ||
141 | |||
142 | {- | ||
143 | Einige Hilfsfunktionen, sowie einige Beispiel-Terme sind weiter unten, | ||
144 | im Anhang dieser Datei definiert. Ebenso wurde eine vernünftige Show-Instanz vorgegeben. | ||
145 | so dass die Terme wie Haskell-Code ausschauen: | ||
146 | *Main> Abs "x" (App (Var "f") (Var "x" )) | ||
147 | \x -> f x | ||
148 | |||
149 | Von den Hilfsfunktionen sollten Sie eigentlich nur | ||
150 | subst :: (Variable, Term) -> Term -> Term | ||
151 | benötigen. Der Ausdruck "subst (x,t1) t2" bedeutet "t2[t1/x]", | ||
152 | also im Ausdruck t2 werden alle Vorkommen von x durch t1 ersetzt. | ||
153 | |||
154 | |||
155 | NEBENBEMERKUNG; | ||
156 | zum Lösen dieser Aufgabe nicht wichtig: | ||
157 | |||
158 | Richtig, es handelt sich dabei erneut um den Lambda-Kalkül, | ||
159 | wie wir ihn schon in Aufgaben A1-1g und A2-2 kennengelernt haben. | ||
160 | Anstatt "\x->x" würde man im Lambda-Kalkül eher "λx.x" schreiben, aber | ||
161 | das ist auch der einzige Unterschied. O | ||
162 | bwohl wir hier auf so viel verzichten, handelt es sich übrigens immer noch um eine Turing-Vollständige Sprache! | ||
163 | Also wollen wir hier die verzögerte Auswertestrategie anhand des Lambda-Kalküls üben... ;) | ||
164 | -} | ||
165 | |||
166 | |||
167 | -- a) Einfache Auswertung | ||
168 | -- | ||
169 | -- Wir schreiben uns zuerst eine simple Auswertung von Lambda-Ausdrücken, also | ||
170 | -- eine Funktion "eval :: Term -> Term". Das vorgehen ist wie folgt: | ||
171 | -- | ||
172 | -- 1) Variablen werten zu sich selbst aus (d.h. sind bereits ausgewertet); nichts zu tun. | ||
173 | -- | ||
174 | -- 2) Abstraktionen sind auch bereits ausgewertet; nichts zu tun. | ||
175 | -- (Wenn man will, dann könnte man auch erst noch den Rumpf auswerten, soweit möglich. | ||
176 | -- Dies ist eine reine Definitionsfrage, darf jeder machen wie er will. | ||
177 | -- Im Allgemeinen wird nicht unter einem Lambda reuduziert, aber beim Testen | ||
178 | -- werden die Terme leichter verständlich, wenn man unter dem Lambda reduziert.) | ||
179 | -- | ||
180 | -- 3) Zum Auswerten einer Applikationen "App" muss man zuerst die Funktion auswerten. | ||
181 | -- Erhält man einen Lambda-Ausdruck "Abs", so ersetzt man alle Vorkommen | ||
182 | -- der abstrahierten Variable durch das zuvor ausgewertete Funktionsargument. | ||
183 | -- (Ansonsten liefert man einfach die gesamte Applikation zurück.) | ||
184 | -- | ||
185 | -- Hinweis: Im 3. Fall muss man noch aufpassen, das keine freien Variablen eingefangen werden. | ||
186 | -- Glücklicherweise ist dies für uns bereits implementiert. Verwenden Sie einfach die Funktion "subst" | ||
187 | -- zur Substitution des Funktionsparameters im Rumpf der Funktion. | ||
188 | -- (Die Funktion "subst" ist weiter unten im Anhang dieser Datei definiert.) | ||
189 | -- | ||
190 | -- Einfache Implementierung der Auswertung : | ||
191 | eval :: Term -> Term | ||
192 | eval = undefined -- !!! TODO !!! | ||
193 | |||
194 | |||
195 | {- Beispiele, ohne Auswertung unter einem Lambda, Konstanten cK, c1, usw. sind weiter unten, im Anhang der Datei definiert. | ||
196 | |||
197 | *Main> eval $ App (App cK c1) vX | ||
198 | \f -> \a -> f a | ||
199 | |||
200 | *Main> eval $ App cISNULL (App cSUCC c0) | ||
201 | \x -> \y -> y | ||
202 | |||
203 | -} | ||
204 | |||
205 | |||
206 | -- b) | ||
207 | -- | ||
208 | -- Da Haskell eine Sprache mit verzögerter Auswertung ist, | ||
209 | -- vererbt sich dies bereits auch auf unsere Implementierung von eval: | ||
210 | -- *Main> eval $ App (App cK c1) cOmega | ||
211 | -- \f -> \x -> f x | ||
212 | -- | ||
213 | -- Bei der Auswertestrategie "Call-By-Value" sollte dies eigentlich gar nicht auswerten, | ||
214 | -- da cOmega nicht endlich auswertbar ist. | ||
215 | -- | ||
216 | -- Eine Möglichkeit ist es, die Auswertung genauer zu simulieren. | ||
217 | -- Dazu nutzen wir jetzt eine Map zur Modellierung unseres Speichers: | ||
218 | |||
219 | type Memory = Map Variable Term | ||
220 | |||
221 | -- Ein Wert des Typs "Memory" bildet also Werte des Typs "Variable" auf Werte des Typs "Term" ab. | ||
222 | -- | ||
223 | -- Zur Anzeige eines Terms benötigen wir nun natürlich auch den Speicher, um den Kontext der Variablen zu haben. | ||
224 | -- Verwenden Sie zur Anzeige also diese gegebene Funktion: | ||
225 | showMem :: (Memory,Term) -> Term | ||
226 | showMem (m,t) = Map.foldlWithKey (\r v s -> subst (v,s) r) t m | ||
227 | |||
228 | -- Diese Anzeige bauchen wir gleich in unsere Auswertefunktion ein: | ||
229 | evalStrict :: Term -> Term | ||
230 | evalStrict t = showMem $ evalS0 t | ||
231 | |||
232 | -- Die Funktion evalS0 ist nur eine Kurzform, um die Auswertung mit leerem Speicher zu starten: | ||
233 | evalS0 ::Term -> (Memory, Term) | ||
234 | evalS0 = evalS Map.empty | ||
235 | |||
236 | -- Ihre Aufgabe ist es also, evalS zu implementieren: | ||
237 | |||
238 | evalS :: Memory -> Term -> (Memory, Term) | ||
239 | evalS = undefined -- !!! TODO !!! | ||
240 | |||
241 | -- Dabei verfolgen wir folgende Auswertestrategie: | ||
242 | -- | ||
243 | -- 1) Der Wert einer Variablen wird im Speicher nachgeschlagen. | ||
244 | -- Freie Variablen werten nach wie vor zu sich selbst aus. | ||
245 | -- | ||
246 | -- 2) Unverändert: Abstraktionen sind bereits ausgewertet. | ||
247 | -- | ||
248 | -- 3) Bei der Auswertung von Applikationen verzichten wir auf Substitution. | ||
249 | -- Stattdessen legen wir das ausgewertete Argument im Speicher | ||
250 | -- unter der abstrahierten Variable ab. | ||
251 | -- | ||
252 | -- Im Fall 3) treten einige Probleme auf: | ||
253 | -- - Der Speicher muss durch die rekursiven Aufruf von evalS hindurchgefädelt werden. | ||
254 | -- Es sollte immer nur auf den neuesten Speicher zugegriffen werden, | ||
255 | -- damit die Modellierung stimmt! | ||
256 | -- | ||
257 | -- - OPTIONAL: Wenn es aber nur einen Speicher gibt, kann es zu Namenskonflikten im Speicher | ||
258 | -- kommen. Dies kann vermieden werden, wenn Variable vor dem Speichern in frische Variablen | ||
259 | -- umbenannt werden. Verwenden Sie dazu die Hilfsfunktionen: freeVars, generateFreshVar & subst. | ||
260 | -- | ||
261 | -- Wenn Sie es richtig gemacht haben, sollte das obige Beispiel mit cOmega jetzt nicht mehr terminieren. | ||
262 | |||
263 | |||
264 | -- c) Nun wollen wir es wieder verbessern, in dem wir verzögerte Auswertung explizit simulieren. | ||
265 | -- | ||
266 | -- Im Fall 1) updaten wir den Speicher nach der Auswertung des abgespeicherten Terms, | ||
267 | -- um wiederholte Auswertung zu vermeiden. | ||
268 | -- (Der Einfachheit verzichten wir auf eine Prüfung/Flag ob im Speicher ein Thunk ist oder nicht: | ||
269 | -- wir werten und updaten immer - wenn es schon ein Wert war, dann geht die Auswertung ja schnell) | ||
270 | -- | ||
271 | -- Fall 2) Abs bleibt Unverändert | ||
272 | -- | ||
273 | -- Im Fall 3) App legen wir also nur den unausgewerten Term im Speicher ab. | ||
274 | -- | ||
275 | |||
276 | evalLazy :: Term -> Term | ||
277 | evalLazy t = showMem $ evalL0 t | ||
278 | |||
279 | evalL0 ::Term -> (Memory, Term) | ||
280 | evalL0 = evalL Map.empty | ||
281 | |||
282 | evalL :: Memory -> Term -> (Memory, Term) | ||
283 | evalL = undefined -- !!! TODO !!! | ||
284 | |||
285 | |||
286 | |||
287 | ----------------------------------------------------------------------- | ||
288 | -- ANHANG | ||
289 | -- | ||
290 | -- Für erweiterten Komfort gegeben wir noch einige Definitionen vor! | ||
291 | -- | ||
292 | |||
293 | |||
294 | -- Hier einige Lambda-Terme zum Testen: | ||
295 | vX = Var "x" | ||
296 | vY = Var "y" | ||
297 | vZ = Var "z" | ||
298 | lTest1 = App (App vX vY) vZ | ||
299 | lTest2 = App vX (App vY vZ) | ||
300 | |||
301 | -- Combinators (allgmein bekannte Lambda-Terme) | ||
302 | cI = Abs "x" $ Var "x" -- \x -> x | ||
303 | cK = Abs "x" $ Abs "y" $ Var "x" -- \x y -> x | ||
304 | cS = Abs "z" $ Abs "y" $ Abs "x" $ App (App (Var "z") (Var "x")) (App (Var "y") (Var "x")) -- \z y x -> (z x) (y x) | ||
305 | cT = Abs "x" $ App (Var "x") (Var "x") -- \x -> x x | ||
306 | cOmega = App cT cT -- (\x -> x x) (\x -> x x) | ||
307 | |||
308 | -- Church Booleans (wer keine eingebauten Datentypen hat, bastelt sich halt selbst welche, so wie es uns Alonzo Church zeigte) | ||
309 | cTRUE = Abs "x" $ Abs "y" $ Var "x" -- \x y -> x | ||
310 | cFALSE = Abs "x" $ Abs "y" $ Var "y" -- \x y -> y | ||
311 | cCOND = Abs "x" $ Abs "y" $ Abs "z" $ App (App vX vY) vZ -- \x y z -> (x y) z | ||
312 | |||
313 | -- Church Numerals (wer keine eingebauten Zahlen hat, kann sich auch diese selbst stricken) | ||
314 | c0 = Abs "f" $ Abs "x" $ Var "x" -- \f x -> x | ||
315 | c1 = Abs "f" $ Abs "x" $ App (Var "f") (Var "x") -- \f x -> f x | ||
316 | c2 = eval $ App cSUCC c1 -- \f -> \x -> f (f x) | ||
317 | c3 = eval $ App cSUCC c2 -- \f -> \x -> f (f (f x)) | ||
318 | cSUCC = Abs "n" $ Abs "f" $ Abs "x" $ App (Var "f") $ App (App (Var "n" ) (Var "f")) (Var "x") -- \n f x -> f ((n f) x) | ||
319 | cPLUS = Abs "m" $ Abs "n" $ Abs "f" $ Abs "x" $ App (App (Var "m") (Var "f")) $ App (App (Var "n" ) (Var "f")) (Var "x") -- \m n f x -> (m f) ((n f) x) | ||
320 | cISNULL = Abs "x" $ App (App (Var "x") (Abs "x" cFALSE)) cTRUE -- \x -> x (\x -> (\x y -> y))) (\x y -> x)) | ||
321 | |||
322 | -- Lambda Terme hübsch anzeigen, in Haskell Notation, also anstatt "λf.λx.f x" schreiben wir hier "\f-> \x-> f x". | ||
323 | instance Show Term where | ||
324 | showsPrec _ (Var x) = showString x | ||
325 | showsPrec n (App s t) = showParen (n>1) $ (showsPrec 1 s) . showString " " . showsPrec 2 t | ||
326 | showsPrec n (Abs x t) = showParen (n>0) $ showString ('\\':x) . showString " -> " . showsPrec n t | ||
327 | |||
328 | ----------------------------- | ||
329 | -- Nützliche Hilfsfunktionen | ||
330 | -- | ||
331 | |||
332 | -- Substitution, welche das Einfangen freier Variablen verhindert. | ||
333 | -- Alle _freien_ Vorkommen einer Variable werden durch einen Term ersetzt. | ||
334 | -- Gebundene Variablen werden ggf. ersetzt, damit es nicht zu Namenskonflikten kommt: | ||
335 | -- [y/x] ( \y -> x y ) == \z -> y z | ||
336 | -- subst ("x", Var "y") (Abs "y" $ App (Var "x") (Var "y")) | ||
337 | -- | ||
338 | -- Wenn wir die Variable "x" durch den Term "y" ersetzen wollen, dann müssen wir | ||
339 | -- aufpassen, dass keine gebundenen Variablen 'eingefangen' werden, denn | ||
340 | -- "\y->x y" ist ja äquivalent zu "\z ->x z". | ||
341 | -- Also soll auch "(\y->x y)[y/x]" äquivalent zu "(\z ->x z)[y/x]" == "\z->y z" sein. | ||
342 | -- Wenn wir aber nur blind einsetzen würden, gilt das nicht, denn wir bekämen "\y->y y". | ||
343 | -- | ||
344 | |||
345 | subst :: (Variable, Term) -> Term -> Term | ||
346 | subst (x,e) o@(Var y) | ||
347 | | x == y = e | ||
348 | | otherwise = o | ||
349 | subst s (App e1 e2) = App (subst s e1) (subst s e2) | ||
350 | subst s@(x,e) o@(Abs y e1) | ||
351 | | x == y = o | ||
352 | | y `Set.notMember` fv_e = Abs y (subst s e1) | ||
353 | | otherwise = Abs freshV (subst (x,e) $ subst (y, Var freshV) e1) -- avoiding capture | ||
354 | where | ||
355 | fv_e = freeVars e | ||
356 | fv_e1 = freeVars e1 | ||
357 | freshV = generateFreshVar (fv_e `Set.union` fv_e1) | ||
358 | |||
359 | |||
360 | -- Freie Variablen eines Terms | ||
361 | freeVars :: Term -> Set Variable | ||
362 | freeVars (Var x) = Set.singleton x | ||
363 | freeVars (App e1 e2) = Set.union (freeVars e1) (freeVars e2) | ||
364 | freeVars (Abs x e1) = Set.delete x $ freeVars e1 | ||
365 | |||
366 | -- Frische Variable berechnen | ||
367 | generateFreshVar :: Set Variable -> Variable | ||
368 | generateFreshVar vs | ||
369 | | v `Set.notMember` vs = v | ||
370 | | otherwise = succString $ Set.findMax vs | ||
371 | where | ||
372 | v = "a" | ||
373 | |||
374 | -- Note that Ord String has "z" > "aa", so succString s = s ++ "a" would suffice | ||
375 | -- Ensure that "s" < succString "s" | ||
376 | succString :: String -> String | ||
377 | succString "" = "a" | ||
378 | succString ('z':s) = 'z' : succString s | ||
379 | succString ( c :s) = (succ c) : s | ||
380 | |||