1 - Korrektur 2 - Funktionen als Daten Wir definieren eine Menge analog zu den Feldern aus der Vorlesung: 1 type Set a = a -> Bool Eine Menge wird also dargestellt durch ihre charakteristische Funktion, die das Enthaltensein berechnet. Die leere Menge enthält natürlich keine Elemente: 2 3 empty :: Set a empty _ = False Ein Element können wir wie folgt in die Menge einfügen: 4 5 insert :: Eq a => a -> Set a -> Set a insert x s y = if x == y then True else s y Wir können genauso gut aber auch eine einelementige Menge konstruieren und diese mit der ursprünglichen Menge per union vereinigen: 6 7 insert' :: Eq a => a -> Set a -> Set a insert' x s = union (\y -> x == y) s Dem schreibfaulen Haskell-Programmierer ist auch diese Lösung noch zu lang, weswegen er zu folgender Form kommt: 8 9 10 insert'' x s = union (x ==) s -- Section für lambda-Ausdruck insert'' x s = union ((==) x) s -- Argument herausziehen insert'' x s = (union . (==)) x s -- Umordnen Nun tauchen x und s auf beiden Seiten nur an letzter Stelle auf, sodass wir diese Parameter auch weglassen können (η-Reduktion): 11 12 insert'' :: Eq a => a -> Set a -> Set a insert'' = union . (==) remove könnten wir wie insert definieren, dabei aber False zurückgeben. Stattdessen führen wir remove aber auf difference zurück: 13 14 remove :: Eq a => a -> Set a -> Set a remove x m = difference m (x ==) Und für die Schreibfaulen: 15 16 17 remove' :: Eq a => a -> Set a -> Set a -- remove' x m = (flip difference) (x==) m remove' = flip difference . (==) isElem entspricht der Anwendung der charakteristischen Funktion auf den zu testenden Wert: 18 19 isElem :: a -> Set a -> Bool isElem x s = s x Auch isElem kann man punktfrei definieren: 20 21 22 23 24 25 isElem' :: a -> Set a -> Bool isElem x s = s $ x -- isElem x s = ($) s x -- isElem x s = flip ($) x s -- isElem = flip ($) -- isElem' = flip id 1 union und intersection kann man implementieren, indem man das logische oder bzw. und auf Mengen überträgt. Dies machen wir mit einer Hilfsfunktion lift: 26 27 union :: Set a -> Set a -> Set a union = lift (||) 28 29 30 intersection :: Set a -> Set a -> Set a intersection = lift (&&) 31 32 33 lift :: (a -> b -> c) -> (d -> a) -> (d -> b) -> d -> c lift op f g x = op (f x) (g x) Das Mengenkomplement entspricht der Negation der charakteristischen Funktion: 34 35 36 37 complement :: Set a -> Set a complement m x = not (m x) -- complement m = not . m -- complement = (not .) Die Differenz zweier Mengen ist die Schnittmenge aus der ersten und dem Komplement der zweiten Menge: 38 39 40 difference :: Set a -> Set a -> Set a difference m n = intersection m (complement n) -- difference m = intersection m . complement Aus einer Liste konstruieren wir eine Menge, indem wir für einen Wert schauen ob er in der Liste enthalten ist: 40 41 42 listToSet :: Eq a => [a] -> Set a listToSet xs x = x `elem` xs -- listToSet = flip elem Vorteile: • einfache Implementierung • endliche Darstellung (einiger) unendlicher Mengen Nachteile: • Zugriffszeit abhängig von der Anzahl vorhergehender Operationen • Teilmengenprädikat nicht implementierbar (ebenso isEmpty oder setToList) 3 - Divide and Conquer Wir definieren zunächst die Funktion dc zum Lösen vom Divide&Conquer-Problemen. Die einzelnen Parameter führen zu der folgenden Typsignatur: 1 2 3 4 5 6 7 dc :: (p -> Bool) -> (p -> [p]) -> (p -> s) -> ([s] -> s) -> p -> s dc trivial divide -- Trivialitätstest -- Aufteilung des Problems -- Lösen eines trivialen Problems -- Kombination von Lösungen -- Problem -- Lösung solve combine p Wenn das Problem trivial ist, so lösen wir es direkt: 8 | trivial p = solve p Ansonsten teilen wir das Problem auf, lösen die Einzelprobleme und kombinieren anschließend die Einzellösungen: 2 9 10 | otherwise = combine $ map conquer $ divide p where conquer = dc trivial divide solve combine Es folgt die Funktion split: 11 12 13 14 split :: (a -> Bool) -> [a] -> ([a], [a]) split _ [] = ([], []) split p (x:xs) = if p x then (x:ys, zs) else (ys, x:zs) where (ys, zs) = split p xs Die findige Studentin wird feststellen, dass man split jedoch auch gut mittels foldr implementieren kann: 15 16 17 split' :: (a -> Bool) -> [a] -> ([a], [a]) split' p = foldr dispatch ([], []) where dispatch x (ys, zs) = if p x then (x:ys, zs) else (ys, x:zs) Mit dc und split können wir nun QuickSort einfach definieren als 18 19 20 21 22 23 24 quickSort :: Ord a => [a] -> [a] quickSort = dc trivial divide id concat where trivial xs = null xs || null (tail xs) divide [] = error "divide: empty list" divide (x:xs) = [smaller, [x], notSmaller] where (smaller, notSmaller) = split (< x) xs Den Trivialitätstest hätte man zwar auch als trivial = (< 2) . length definieren können, dies wäre aber insbesondere für große Listen sehr ineffizient! Ein Element im Suchbaum können wir ebenfalls mit Hilfe von dc suchen: data SearchTree = Empty | Branch SearchTree Int SearchTree deriving (Eq, Show) elemTree :: Int -> SearchTree -> Bool elemTree e t = dc trivial divide solve or t where trivial Empty = True trivial (Branch lt _ rt) = lt == Empty && rt == Empty solve Empty = False solve (Branch _ v _ ) = e == v divide Empty = error "divide: empty tree" divide (Branch lt v rt) | e < v = [lt] | e > v = [rt] | otherwise = [Branch Empty v Empty] 4 - Lambdas in Java 1 2 3 import java.util.ArrayList; import java.util.Arrays; import java.util.List; 4 5 public class Function { 6 7 8 9 interface Fun<T, U> { public U apply(T t); } 3 10 11 12 13 interface Fun2<T, U, V> { public V apply(T t, U u); } 14 15 16 17 18 19 20 21 public static <T, U> List<U> map(Fun<T, U> f, List<T> list) { List<U> res = new ArrayList<>(list.size()); for (T t : list) { res.add(f.apply(t)); } return res; } 22 23 24 25 26 27 28 29 public static <T, U, V> Fun<T, V> compose(Fun<U, V> f, Fun<T, U> g) { return new Fun<T, V>() { public V apply(T t) { return f.apply(g.apply(t)); } }; } 30 31 32 33 34 35 36 37 public static <A, B> B foldr(Fun2<A, B, B> f, B e, List<A> xs) { if (xs.isEmpty()) { return e; } else { return f.apply(xs.get(0), foldr(f, e, xs.subList(1, xs.size()))); } } 38 39 40 41 42 43 44 45 public static <A, B> B foldl(Fun2<A, B, B> f, B e, List<A> xs) { B res = e; for (A a : xs) { res = f.apply(a, res); } return res; } 46 47 48 49 public static void main(String[] args) { List<Integer> a = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); 50 Fun<Integer, Integer> square = x -> x * x; List<Integer> b = map(compose(square, square), a); System.out.println(b); 51 52 53 54 Integer sum = foldr((x, y) -> x + y, 0, a); System.out.println(sum); 55 56 57 sum = foldl((x, y) -> x + y, 0, a); System.out.println(sum); 58 59 60 61 62 63 64 65 } List<Fun<Integer, Integer>> incs = map(x -> y -> x + y, a); for (Fun<Integer, Integer> inc : incs) { System.out.println(inc.apply(0)); } 66 4 67 } 5 - Kombinatorfunktionen in Haskell Ausdrücke ohne Lambda • Der Ausdruck \x y -> f (g x) y entspricht f . g, denn \x y -> f (g x) y = \x -> f (g x) = \x -> (f . g) x = f . g • Der Ausdruck \x y -> f (g (h x y)) entspricht ((f . g) .) . h, denn = = = = = = = = = \x y -> f (g (h x y)) \x y -> f (g ((h x) y)) \x y -> f ((g . h x) y) \x y -> (f . (g . h x)) y \x -> f . (g . h x) \x -> f . (((.) g . h) x) \x -> ((.) f . ((.) g . h)) x (.) f . ((.) g . h) (f.) . (g.) . h ((f . g) .) . h • Der Ausdruck \f g x -> g (f x) entspricht flip (.), denn = = = = = = \f g x -> g (f x) \f g x -> (g . f) x \f g -> (g . f) \f g -> (.) g f \f g -> flip (.) f g \f -> flip (.) f flip (.) Ausdrücke mit Lambda • Der Ausdruck flip id entspricht \x f -> f x, denn = = = = = flip \x \x f \x f \x f id -> -> -> -> flip id x flip id x f id f x f x • Der Ausdruck (.) . (.) entspricht \f g x y -> f (g x y), denn = = = = = = = = = (.) . (.) \f -> \f -> \f g -> \f g -> \f g x -> \f g x -> \f g x y -> \f g x y -> ((.) . (.)) f (.) (f.) (.) (f.) g (f.) . g ((f.) . g) x (f.) (g x) (f.) (g x) y f (g x y) zipWith 5 1 2 zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] zipWith f xs ys = map (uncurry f) (zip xs ys) 6