Softwerkskammer

 

Chapter 4. Functional programming (III)

  • Anonymous (lambda) functions
  • Partial function application and currying
    • Sections
  • As-patterns
  • Code reuse through composition
    • Use your head wisely
  • Tips for writing readable code
  • Space leaks and strict evaluation
    • Avoiding space leaks with seq
    • Learning to use seq

Anonymous (lambda) functions

isInAny needle haystack = any inSequence haystack
    where inSequence s = needle `isInfixOf` s

isInAny2 needle haystack = any (\s -> needle `isInfixOf` s) haystack

inc = \x -> x + 1

plus = \a b -> a + b

three = (\x -> x + 1) 2
  • Durch Backslash eingeleitet (symbolisiert λ)
  • Keine Guards erlaubt
    • Vorsicht mit unvollständigen Patterns
  • Oft schlechtere Lesbarkeit als z.B.
    • lokale Funktionen mit let oder where
    • Partial function application
    • Sections

Partial function application and currying

  • Alle Funktionen nehmen genau ein Argument
    • Geben ggf. Funktion zurück, die das nächste Argument nimmt
  • Parameterliste teilweise fixierbar
    • Spezialisierte Funktion entsteht
ghci> :type dropWhile
dropWhile :: (a -> Bool) -> [a] -> [a]

ghci> :type dropWhile isSpace
dropWhile isSpace :: [Char] -> [Char]

ghci> map (dropWhile isSpace) [" a","f","   e"]
["a","f","e"]
  • Nicht zu verwechseln mit partial functions

Partial function application and currying

  • Currying /= Partial function application (?)

    • Haskell hat Currying eingebaut
  • Point-free style oder Tacit programming

niceSum :: [Integer] -> Integer
niceSum xs = foldl (+) 0 xs

niceSum2 :: [Integer] -> Integer
niceSum2 = foldl (+) 0

Sections

  • Spezialfall von Partial function application
  • Geklammerte Schreibweise für infix Notation
    • Linker oder rechter Operand fixierbar
ghci> (1+) 2
3
ghci> map (*3) [24,36]
[72,108]
ghci> map (2^) [3,5,7,9]
[8,32,128,512]

ghci> :type elem
elem :: Eq a => a -> [a] -> Bool
ghci> :type (`elem` ['a'..'z'])
(`elem` ['a'..'z']) :: Char -> Bool

As-Patterns

  • Pattern matching erlaubt Dekonstruieren von Listen
    • Liste wird in Bestandteile zerlegt
    • Problem: ursprüngliche Liste ist verloren
  • Schlechte Lösung: Liste wieder zusammenbauen
    • Codeduplikation
    • Overhead
  • Gute Lösung: ursprünglicher Liste einen Namen geben
    • @ zwischen Name und Pattern
suffixes :: [a] -> <a class="internal" href="/wiki/lernfp/a">a</a>
suffixes xs@(_:xs') = xs : suffixes xs'
suffixes _ = []

Code reuse through composition

suffixes2 xs = init (tails xs)

compose f g x = f (g x)

suffixes3 xs = compose init tails xs
suffixes4 = compose init tails


(.) :: (b -> c) -> (a -> b) -> a -> c

suffixes5 = init . tails


capCount = length . filter (isUpper . head) . words
capCount "Hello there, Mom!" == 2
  • (.) ist rechtsassoziativ
  • Kette von Funktionen wird rechts nach links angewandt

Tips for writing readable code

  • Schleifen
    • Welche Alternative bietet die beste Lesbarkeit?
      1. Komposition von Standardfunktionen
      2. fold
      3. Explizite Rekursion
  • Anonyme Funktionen
    • Bessere Alternativen:
      1. lokale Funktionen mit let oder where
    1. Partial function application / Sections

Space leaks and strict evaluation

  • Welches fold für welche Anwendungsfälle?
    • foldr für Erzeugen von Listen
    • foldl' ansonsten
      • foldl meiden

Avoiding space leaks with seq

seq :: a -> t -> t

foldl' _    zero []     = zero
foldl' step zero (x:xs) =
    let new = step zero x
    in  new `seq` foldl' step new xs
  • erzwingt Evaluierung des ersten Arguments
  • gibt das zweite Argument zurück
  • Magie (nicht implementiert in Haskell)

Learning to use seq

  • seq muss innerhalb eines Ausdrucks zuerst evaluiert werden
    • sonst wird Ergebnis wieder in thunk verpackt
  • seq hat keinen Effekt auf duplizierte Ausdrücke
  • Fehlerhafte Beispiele:
hiddenInside x y = someFunc (x `seq` y)

hiddenByLet x y z = let a = x `seq` someFunc y
                    in anotherFunc a z

badExpression step zero (x:xs) =
    seq (step zero x)
        (badExpression step (step zero x) xs)

Learning to use seq (II)

  • seq verketten um mehrere Ausdrücke zu evaluieren:
chained x y z = x `seq` y `seq` someFunc z
  • seq stoppt bei Konstruktoren
    • Workarounds für Listen, Tupel:
strictPair (a,b) = a `seq` b `seq` (a,b)

strictList (x:xs) = x `seq` x : strictList xs
strictList []     = []
  • seq ist kein Allheilmittel

Tests

  • Hspec
  • QuickCheck (Property-based testing)