Monaden Funktionale Programmierung Monaden D. Rösner Institut für Wissens- und Sprachverarbeitung Fakultät für Informatik Otto-von-Guericke Universität Magdeburg c Sommer 2014, 16. Mai 2014, 2011-14 D.Rösner D. Rösner FP 2014 . . . 1 Monaden Gliederung 1 Monaden Klasse Monad Tree Maybe Liste MonadPlus D. Rösner FP 2014 . . . 2 Monaden Klasse Monad Tree Maybe Liste MonadPlus Monaden in Haskell Verallgemeinerung der Prinzipien von I/O Möglichkeit zum Sequentialisieren von Berechnungen drei relevante Klassen: die Klasse Functor (aus dem Standard Prelude) die Klasse Monad (aus dem Standard Prelude) die Klasse MonadPlus (aus Bibliothek Monad) D. Rösner FP 2014 . . . 4 Monaden Klasse Monad Tree Maybe Liste MonadPlus Die Klasse Monad die Signatur umfasst vier Grundoperationen class Monad m return :: (>>=) :: (>>) :: fail :: where a -> m m a -> m a -> String a (a -> m b) -> m b m b -> m b -> m a D. Rösner FP 2014 . . . 5 Monaden Klasse Monad Tree Maybe Liste MonadPlus Die Klasse Monad cont. für (>>) (sprich: sequence) und fail gibt es Default-Implementationen: p >> q fail s = p >>= \ _ -> q = error s für Instanzen reicht es meist aus, (>>=) (sprich: bind oder then) und return zu definieren D. Rösner FP 2014 . . . 6 Monaden Klasse Monad Tree Maybe Liste MonadPlus Die Klasse Monad cont. zur Verwendung der vier Grundoperationen: mit return wird ein Wert als monadischer Wert ohne weitere Aktion zurückgegeben mit (>>=) werden Berechnungen sequentialisiert, wobei ein Wert von der einen zur anderen Berechnung weitergereicht wird mit (>>) werden Berechnungen sequentialisiert, wobei die zweite den Wert der ersten Berechnung nicht benötigt fail s ist eine Berechnung, die fehlschlägt und die Fehlermeldung s liefert D. Rösner FP 2014 . . . 7 Monaden Klasse Monad Tree Maybe Liste MonadPlus Die Klasse Monad cont. Instanzen der Klasse Monad müssen bestimmten Gesetzen genügen mit dem abgeleiteten Operator >@> (sog. Kleisli-Komposition) lauten diese: M1: M2: M3: return >@> f = f f >@> return = f (f >@> g) >@> h = f >@> (g >@> h) m.a.W.: return ist ein neutrales Element (oder Identität) für >@> (M1, M2) und >@> ist assoziativ (M3) D. Rösner FP 2014 . . . 8 Monaden Klasse Monad Tree Maybe Liste MonadPlus Die Klasse Monad cont. Definition des abgeleiteten Operator >@> (sog. Kleisli-Komposition): (>@>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c) f >@> g = \x -> (f x) >>= g Verallgemeinerung der Funktionskomposition D. Rösner FP 2014 . . . 9 Monaden Klasse Monad Tree Maybe Liste MonadPlus Die Klasse Monad cont. mit (»=) lauten die Monadengesetze wie folgt: M1’: M2’: M3’: return a >>= k = k a m >>= return = m (m >>= k) >>= h = m >>= (\x -> k x >>= h) Interpretation M1’: return schickt seinen Wert an die nächste Aktion M2’: wenn eine Aktion direkt von einem return gefolgt, dann kann auch die Aktion selbst den Wert zurückgeben M3’: eine Form von Assoziativität von (»=) D. Rösner FP 2014 . . . 10 Monaden Klasse Monad Tree Maybe Liste MonadPlus Beziehung zwischen Monaden und do-Notation die do-Notation ist nur eine Kurzschreibweise für die Anwendung der monadischen Operatoren einige der dabei geltenden Übersetzungsregeln: do e1 ==> e1 do e1; e2; ...; eN ==> e1 >> do e2; ...; eN do x <- e1; e2; ...; eN ==> e1 >>= \x -> do e2; ...; eN D. Rösner FP 2014 . . . 11 Monaden Klasse Monad Tree Maybe Liste MonadPlus Beziehung zwischen Monaden und do-Notation die Monadengesetze nehmen dann die folgende Gestalt an (vgl. [Hud00]): M1”: M2”: M3”: do x <- return a; k x = k a do x <- m; return x = m do x <- m; y <- k x; h y = do y <- (do x <- m; k x); h y dem Assoziativgesetz für (>>) entspricht: do m1; m2; m3 = do (do m1; m2); m3 D. Rösner FP 2014 . . . 12 Monaden Klasse Monad Tree Maybe Liste MonadPlus Instanzen der Klasse Monad generell gilt: Elemente einer Monade m a sind Berechnungen, die gewisse Aktionen ausführen können, bevor sie einen Wert vom Typ a zurückgeben (vgl. [Tho99], p. 404]) für unterschiedliche Instanzen der Klasse Monad sind anzugeben: Definitionen für zumindest (>>=) und return (die den Monadengesetzen genügen), die spezialisierten Typen dieser Operationen, eine Interpretation der besonderen Eigenschaften der Monade als Berechnung. D. Rösner FP 2014 . . . 13 Monaden Klasse Monad Tree Maybe Liste MonadPlus Anwendung von Monad auf Bäume [cf. [Tho99], Ch. 18.9] binäre Bäume als rekursive Datentypen data Tree a = Nil | Node a (Tree a) (Tree a) deriving (Eq, Ord, Show, Read) ein Beispiel eines Baumes: tree1 = Node "Moon" (Node "Ahmet" Nil Nil) (Node "Dweezil" (Node "Ahmet" Nil Nil) (Node "Moon" Nil Nil)) D. Rösner FP 2014 . . . 15 Monaden Klasse Monad Tree Maybe Liste MonadPlus Aufgabe: Nummerieren eines beliebigen Baumes Aufgabe numTree: ersetze die Knoten eines beliebigen Baumes durch Zahlen; dabei sollen identische Knoten jeweils durch dieselbe Zahl ersetzt werden (cf. [Tho99], pp. 409) D. Rösner FP 2014 . . . 16 Monaden Klasse Monad Tree Maybe Liste MonadPlus Aufgabe: Nummerieren eines beliebigen Baumes eine mögliche Lösung: traversiere den Baum und kreiere dabei eine Tabelle, die Knoten mit ganzen Zahlen assoziiiert traversiere den Baum erneut mit dieser Tabelle und ersetze jeden Knoten durch die assoziiierte Zahl Nachteil dieser Lösung: Baum wird zweimal traversiert D. Rösner FP 2014 . . . 17 Monaden Klasse Monad Tree Maybe Liste MonadPlus Details der direkten Lösung -- a Table associates elements with zero-based positions type Table a = [a] -- traverse a tree and return the Table of its elements tree2table (Node root left right) = t2t right (t2t left [root]) where t2t tree table = case tree of Nil -> table (Node root1 l1 r1) -> t2t r1 (t2t l1 (update root1 table)) D. Rösner FP 2014 . . . 18 Monaden Klasse Monad Tree Maybe Liste MonadPlus Details der direkten Lösung Aktualisieren der Tabelle, falls neuer Knoten gefunden: update node table = if elem node table then table else table ++ [node] Anwendung: Main> tree2table tree1 ["Moon","Ahmet","Dweezil"] D. Rösner FP 2014 . . . 19 Monaden Klasse Monad Tree Maybe Liste MonadPlus Details der Lösung -- a direct implementation of numTree numTree’ tree = convert tree (tree2table tree) where convert tree table = case tree of Nil -> Nil (Node root1 l1 r1) -> (Node (lookup’ root1 table) (convert l1 table) (convert r1 table)) D. Rösner FP 2014 . . . 20 Monaden Klasse Monad Tree Maybe Liste MonadPlus Details der direkten Lösung -- lookup is in Prelude.hs lookup’ elem tableG -- for the error message (cf. below) = look elem tableG 0 where look elem table pos = case table of [] -> error ("from lookup’: table " ++ show tableG ++ " does not contain elem " ++ show elem) x:xs -> if x == elem then pos else look elem xs (pos + 1) D. Rösner FP 2014 . . . 21 Monaden Klasse Monad Tree Maybe Liste MonadPlus Details der direkten Lösung Main> tree1 Node "Moon" (Node "Ahmet" Nil Nil) (Node "Dweezil" (Node "Ahmet" Nil Nil) (Node "Moon" Nil Nil)) Main> numTree’ tree1 Node 0 (Node 1 Nil Nil) (Node 2 (Node 1 Nil Nil) (Node 0 Nil Nil)) D. Rösner FP 2014 . . . 22 Monaden Klasse Monad Tree Maybe Liste MonadPlus Eine monadische Lösung [cf. [Tho99], Ch. 18.9] Grundidee: ein Zustand enthält eine Funktion, die einer Tabelle (mit Werten vom Typ a) ein Paar zuordnet aus einer möglicherweise veränderten Tabelle und einem Ergebniswert (vom Typ b) data State a b = State (Table a -> (Table a, b)) instance Monad (State a) where return x = State (\tab -> (tab,x)) (State st) >>= f = State (\tab -> let (newTab,y) = st tab (State trans) = f y in trans newTab) D. Rösner FP 2014 . . . 23 Monaden Klasse Monad Tree Maybe Liste MonadPlus Eine monadische Lösung ein Objekt x wird in eine Monadeninstanz als Funktion \tab -> (tab,x) eingebracht return x = State (\tab -> (tab,x)) Erläuterung zur Definition von >>= die Anwendung der Funktion st aus (State st) auf eine Tabelle tab liefert ein Paar (newTab,y) wird f auf y angewendet, so ergibt dies eine Monadeninstanz (State trans) die Funktion trans aus dieser Monade wird dann auf newTab angewendet D. Rösner FP 2014 . . . 24 Monaden Klasse Monad Tree Maybe Liste MonadPlus Eine monadische Lösung numberNode x = State (nNode x) nNode x table | elem x table = (table , lookup’ x table) | otherwise = (table++[x], length table) Main> :t numberNode numberNode :: (Show a, Eq a) => a -> State a Int Main> :t nNode nNode :: (Eq a, Show a) => a -> [a] -> ([a],Int) D. Rösner FP 2014 . . . 25 Monaden Klasse Monad Tree Maybe Liste MonadPlus Eine monadische Lösung numberTree Nil = return Nil numberTree (Node x t1 t2) = do num <nt1 <nt2 <return numberNode x numberTree t1 numberTree t2 (Node num nt1 nt2) Main> :t numberTree numberTree :: (Eq a, Show a) => Tree a -> State a (Tree Int) D. Rösner FP 2014 . . . 26 Monaden Klasse Monad Tree Maybe Liste MonadPlus Eine monadische Lösung Frage: wie lässt sich do num <nt1 <nt2 <return numberNode x numberTree t1 numberTree t2 (Node num nt1 nt2) schreiben mit Operator >>= ? D. Rösner FP 2014 . . . 27 Monaden Klasse Monad Tree Maybe Liste MonadPlus Eine monadische Lösung den Rückgabewert erhält man, indem man die im Zustand (State st) enthaltene Funktion st auf eine leere Tabelle anwendet; dies liefert ein Paar aus (veränderter) Tabelle und dem Rückgabewert als zweites Element extract (State st) = snd (st []) numTree :: (Show a, Eq a) => Tree a -> Tree Int numTree = extract . numberTree D. Rösner FP 2014 . . . 28 Monaden Klasse Monad Tree Maybe Liste MonadPlus Eine monadische Lösung Verwendung: Main> numTree tree1 Node 0 (Node 1 Nil Nil) (Node 2 (Node 1 Nil Nil) (Node 0 Nil Nil)) D. Rösner FP 2014 . . . 29 Monaden Klasse Monad Tree Maybe Liste MonadPlus Variante: gleichzeitig Baum nummerieren und spiegeln numberAndMirrorTree Nil = return Nil numberAndMirrorTree (Node x t1 t2) = do num <- numberNode x nt1 <- numberAndMirrorTree t1 nt2 <- numberAndMirrorTree t2 return (Node num nt2 nt1) -- mirroring! numAndMirrorTree :: (Show a, Eq a) => Tree a -> Tree Int numAndMirrorTree = extract . numberAndMirrorTree D. Rösner FP 2014 . . . 30 Monaden Klasse Monad Tree Maybe Liste MonadPlus Variante: gleichzeitig Baum nummerieren und spiegeln Main> numAndMirrorTree tree1 Node 0 (Node 2 (Node 0 Nil Nil) (Node 1 Nil Nil)) (Node 1 Nil Nil) Main> tree1 Node "Moon" (Node "Ahmet" Nil Nil) (Node "Dweezil" (Node "Ahmet" Nil Nil) (Node "Moon" Nil Nil)) Main> numTree tree1 Node 0 (Node 1 Nil Nil) (Node 2 (Node 1 Nil Nil) (Node 0 Nil Nil)) D. Rösner FP 2014 . . . 31 Monaden Klasse Monad Tree Maybe Liste MonadPlus numTree: Vergleich mit Lösung ohne Monaden numTree’’ tree = ntree where (ntree, table) = traverseConvert (tree,[]) traverseConvert pair = case pair of (Nil, table) -> (Nil, table) ((Node root1 l1 r1), table) -> ((Node (lookup’ root1 tabler1) nl1 nr1), tabler1) where (nl1, tablel1) = traverseConvert (l1, (update root1 table)) (nr1, tabler1) = traverseConvert (r1, tablel1) D. Rösner FP 2014 . . . 32 Monaden Klasse Monad Tree Maybe Liste MonadPlus Lösung ohne Monaden Wie ist numTree” abzuändern für eine nonmonadische Lösung für numAndMirrorTree? D. Rösner FP 2014 . . . 33 Monaden Klasse Monad Tree Maybe Liste MonadPlus Zusammenfassung die monadische Lösung erlaubt es, die Behandlung der Tabelle in den Zuständen und den Zustandsübergängen zu ‘verbergen’ ohne Monaden müssen die Zustände explizit zusammen mit der eigentlichen Aufgabenstellung bearbeitet werden m.a.W.: Monaden als Mittel zur Abstraktion und Modularisierung D. Rösner FP 2014 . . . 34 Monaden Klasse Monad Tree Maybe Liste MonadPlus Maybe als Instanz der Klasse Monad Elemente der Monade Maybe a sind Berechnungen, die entweder einen Wert vom Typ a haben oder einen Fehler produzieren können aus dem Standard Prelude: instance Monad Maybe where Just x >>= k = k x Nothing >>= k = Nothing return = Just fail s = Nothing D. Rösner FP 2014 . . . 36 Monaden Klasse Monad Tree Maybe Liste MonadPlus Maybe als Monad cont. wollte man zwei Funktionen f :: a -> b und g :: b -> c hintereinander ausführen, bei denen jeweils auch Fehler auftreten können und daher die Resultate mit dem Datentyp Maybe angegeben werden, so wäre ohne monadische Operatoren die Fehlerbehandlung bei g (f x) z.B. wie folgt (vgl. [Hud00], p. 256): case (f x) of Nothing -> Nothing Just y -> case (g y) of Nothing -> Nothing Just z -> z D. Rösner FP 2014 . . . 37 Monaden Klasse Monad Tree Maybe Liste MonadPlus Maybe als Monad cont. mit monadischen Operatoren vereinfacht sich dies zu: f x >>= \y -> g y >>= \z -> return z in do-Notation: do y <- f x; z <- g y; return z D. Rösner FP 2014 . . . 38 Monaden Klasse Monad Tree Maybe Liste MonadPlus Maybe als Monad cont. Vereinfachungen (vgl. [Hud00], pp. 256/257): f x >>= \y -> g y >>= \z -> return z sog. ’currying simplication’ f x >>= \y -> g y >>= return Monadengesetz für return f x >>= \y -> g y erneut ’currying simplication’ f x >>= g D. Rösner FP 2014 . . . 39 Monaden Klasse Monad Tree Maybe Liste MonadPlus Maybe als Monad cont. m.a.W.: aus g(f x) wurde f x >>= g weitere Abstraktion als monadischer Kompositionsoperator (vgl. [Hud00], p. 257): composeM :: Monad m => (b -> mc) -> (a -> mb) -> (a -> mc) (g ‘composeM‘ f) x = f x >>= g D. Rösner FP 2014 . . . 40 Monaden Klasse Monad Tree Maybe Liste MonadPlus Datentyp Liste als Instanz von Monad aus dem Standard Prelude: instance Monad [ ] where (x:xs) >>= f = f x ++ (xs >>= f) [] >>= f = [] return x = [x] fail s = [] gleichwertige Definition: xs >>= f = concat (map f xs) D. Rösner FP 2014 . . . 42 Monaden Klasse Monad Tree Maybe Liste MonadPlus Datentyp Liste als Instanz von Monad die Typen der monadischen Operatoren sind dann: (>>=) :: [a] -> (a -> [b]) -> [b] return :: a -> [a] die Monade [] lässt sich als Realisation nichtdeterministischer Berechnungen sehen: ein Element von [a] repräsentiert alle Ergebnisse einer Berechnung Fehlschlag ergibt eine leere Resultatliste D. Rösner FP 2014 . . . 43 Monaden Klasse Monad Tree Maybe Liste MonadPlus Datentyp Liste als Instanz von Monad die folgenden Darstellungen sind damit gleichwertig: do x <- [1,2,3]; y <- [4,5,6]; return (x,y) [1,2,3] >>= (\x -> [4,5,6] >>= (\y -> return (x,y))) [(x,y) | x <- [1,2,3], y <- [4,5,6]] D. Rösner FP 2014 . . . 44 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen Beispiel: simple DB mit Telefonnummern von Personen (cf. [OGS09], Ch. 15) -- file: ch15/VCard.hs data Context = Home | Mobile | Business deriving (Eq, Show) type Phone = String albulena = [(Home, "+355-652-55512")] nils = [(Mobile, "+47-922-55-512"), (Business, "+47-922-12-121"), (Home, "+47-925-55-121"), (Business, "+47-922-25-551")] twalumba = [(Business, "+260-02-55-5121")] D. Rösner FP 2014 . . . 46 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen Annahme: für persönliches Telefonat die private ode mobile Nummer gesucht, nicht aber die geschäftliche -- file: ch15/VCard.hs onePersonalPhone :: [(Context, Phone)] -> Maybe Phone onePersonalPhone ps = case lookup Home ps of Nothing -> lookup Mobile ps Just n -> Just n cf. [OGS09], Ch. 15 D. Rösner FP 2014 . . . 47 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen Verwendung von Liste, um mehrere Nummern zurückgeben zu können -- file: ch15/VCard.hs allBusinessPhones :: [(Context, Phone)] -> [Phone] allBusinessPhones ps = map snd numbers where numbers = case filter (contextIs Business) ps of [] -> filter (contextIs Mobile) ps ns -> ns contextIs a (b, _) = a == b cf. [OGS09], Ch. 15 D. Rösner FP 2014 . . . 48 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen beide Funktionen haben gleiche Struktur im case: zuerst leeres Resultat behandeln, dann nicht-leeres Resultat Verwendung: ghci> onePersonalPhone twalumba Nothing ghci> onePersonalPhone albulena Just "+355-652-55512" ghci> allBusinessPhones nils ["+47-922-12-121","+47-922-25-551"] cf. [OGS09], Ch. 15 D. Rösner FP 2014 . . . 49 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen Definitionen von [] und Maybe als Instanzen von MonadPlus (cf. [OGS09], Ch. 15) -- file: instance mzero mplus ch15/VCard.hs MonadPlus [] where = [] = (++) instance MonadPlus Maybe where mzero = Nothing Nothing ‘mplus‘ ys = ys xs ‘mplus‘ _ = xs D. Rösner FP 2014 . . . 50 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen Redefinition: -- file: ch15/VCard.hs oneBusinessPhone :: [(Context, Phone)] -> Maybe Phone oneBusinessPhone ps = lookup Business ps ‘mplus‘ lookup Mobile ps allPersonalPhones :: [(Context, Phone)] -> [Phone] allPersonalPhones ps = map snd $ filter (contextIs Home) ps ‘mplus filter (contextIs Mobile) ps cf. [OGS09], Ch. 15 D. Rösner FP 2014 . . . 51 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen normales lookup: -- file: ch15/VCard.hs lookup :: (Eq a) => a -> [(a, b)] -> Maybe b lookup _ [] = Nothing lookup k ((x,y):xys) | x == k = Just y | otherwise = lookup k xys cf. [OGS09], Ch. 15 D. Rösner FP 2014 . . . 52 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen Redefinition als lookupM für beliebige Instanzen von MonadPlus: -- file: ch15/VCard.hs lookupM :: (MonadPlus m, Eq a) => a -> [(a, b)] -> m b lookupM _ [] = mzero lookupM k ((x,y):xys) | x == k = return y ‘mplus‘ lookupM k xys | otherwise = lookupM k xys cf. [OGS09], Ch. 15 D. Rösner FP 2014 . . . 53 Monaden Klasse Monad Tree Maybe Liste MonadPlus Motivation: Umgang mit Alternativen Beachte: mplus ist nicht immer eine ’Addition’ Prelude> import Control.Monad Prelude Control.Monad> [1,2,3] ‘mplus‘ [4,5,6] [1,2,3,4,5,6] Prelude Control.Monad> Just 1 ‘mplus‘ Just 2 Just 1 cf. [OGS09], Ch. 15 D. Rösner FP 2014 . . . 54 Monaden Klasse Monad Tree Maybe Liste MonadPlus Die Klasse MonadPlus manche Monaden besitzen eine Operation mplus, mit der monadische Werte aus getrennten Berechnungen in einen monadischen Wert kombiniert werden können, und ein neutrales Element mzero für diesen Operator es müssen dann die folgenden zusätzlichen Gesetze gelten: M+1: M+2: M+3: M+1: mzero >>= f = mzero m >>= (\x -> mzero) = mzero mzero ‘mplus‘ m = m m ‘mplus‘ mzero = m D. Rösner FP 2014 . . . 55 Monaden Klasse Monad Tree Maybe Liste MonadPlus Die Klasse MonadPlus Monaden mit mplus und mzero sollten als Instanzen der Klasse MonadPlus deklariert werden Definition: (aus Monad.hs) class Monad m => MonadPlus m where mzero :: m a mplus :: m a -> m a -> m a D. Rösner FP 2014 . . . 56 Monaden Klasse Monad Tree Maybe Liste MonadPlus Instanzen der Klasse MonadPlus Maybe wird zur Instanz der Klasse MonadPlus durch: instance MonadPlus Maybe mzero = Nothing ‘mplus‘ ys = xs ‘mplus‘ ys = where Nothing ys xs m.a.W. ’Addieren’ zweier Werte vom Typ Maybe ergibt den ersten Wert, der nicht Nothing, oder Nothing, falls beide Werte Nothing D. Rösner FP 2014 . . . 57 Monaden Klasse Monad Tree Maybe Liste MonadPlus Instanzen der Klasse MonadPlus bei der Monade für Listen ist mzero die leere Liste und die ’Addition’ mplus ist die Listenkonkatenation Definition: instance MonadPlus [ ] where mzero = [] mplus = (++) D. Rösner FP 2014 . . . 58 Monaden Klasse Monad Tree Maybe Liste MonadPlus Literatur: I Paul Hudak. The Haskell School of Expression – Learning Functional Programming through Multimedia. Cambridge University Press, Cambridge, UK, 2000. ISBN 0-521-64338-4. Bryan O’Sullivan, John Goerzen, and Don Stewart. Real World Haskell. O’Reilly Media, Sebastopol, CA 95472, 2009. ISBN 978-0-596-51498-3; online available at http://book.realworldhaskell.org/. D. Rösner FP 2014 . . . 59 Monaden Klasse Monad Tree Maybe Liste MonadPlus Literatur: II Simon Thompson. Haskell - The Craft of Functional Programming. Addison Wesley Longman Ltd., Essex, 1999. 2nd edition, ISBN 0-201-34275-8; Accompanying Web site: http://www.cs.ukc.ac.uk/people/staff/sjt/craft2e. D. Rösner FP 2014 . . . 60