>>> Lists >> reminder: * list: *container data type* for arbitrarily many elements * lists are *homogeneous*: all elements have the same type * order matters: two lists are equal if they contain the same elements in the same order * Syntax [ ] -- empty list (x : xs) -- list with head x and tail xs, x :: a and xs :: [a] (1 : (2 : (3 : []))) == (1 : 2 : 3 : []) == [1, 2, 3] ['h', 'e', 'l', 'l', 'o'] == "hello" type String = [Char] >>> In principle, lists could be defined using data data List = Empty | Add ??? List * Cannot put a concrete type for ??? because List should be available for all types >>> Solution: define a *parametric type* data List a = Empty | Add a List * definition includes a *type parameter* 'a' * can be used where a type is expected on the rhs * each use of the 'List a' datatype instantiates 'a' to a type Add 3 (Add 2 Empty) :: List Int Add 'x' (Add 'y' Empty) :: List Char >>> Haskell lists *** attention pseudocode: data [a] = [ ] | a : [a] >>> Further predefined datatypes * Maybe * Either * Ordering >>> Parametric types => parametric functions * functions that work on parametric types are often applicable to all instances of the type * ==> *polymorphic functions* * their type contains type parameters * examples for polymorphic functions on lists: filter :: (a -> Bool) -> [a] -> [a] reverse :: [a] -> [a] -- reverses the order of elements in a list (++) :: [a] -> [a] -> [a] -- list concatenation concat :: [[a]] -> [a] -- list flattening take :: Int -> [a] -> [a] -- prefix of length n drop :: Int -> [a] -> [a] -- drop prefix of length n zip :: [a] -> [b] -> [(a, b)] -- zip two lists together >>> Restricted polymorphism * some functions work on parametric types, but are only applicable to some instances * their type contains type parameters and *type predicates* (or type constraints) * examples for type constraints: Eq a => ... -- type a must have an equality Ord a => ... -- type a must have an ordering Num a => ... -- type a has numeric operators >>> Example: a sorting function for lists qsort :: [a] -> [a] * use "where" * each constraint mentions a *type class* * a type class specifies a set of operations for a type * type classes form a hierarchy * many type classes are predefined in Haskell >>> Example type class -- class declaration: define the name of the class, its operations, and their types -- (from Prelude) -- expected to be an equivalence relation class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool -- instance declaration: specify if a type belongs to a class instance Eq Int where x == y = primEqInt x y instance Eq Char where x == y = primEqInt (ord x) (ord y) -- to test equality of a list, we need to test elements for equality instance Eq a => Eq [a] where [] == [] = True (x:xs) == (y:ys) = x == y && xs == ys _ == _ = False * what about (/=) ? >>> Example building a type class hierarchy * only things that have an equality test can be ordered class Eq a => Ord a where (<) :: a -> a -> Bool (<=) :: a -> a -> Bool -- requires Eq Int instance Ord Int where x < y = primLtInt x y instance Ord (a, b) where (x1, x2) < (y1, y2) = ??? instance Ord [a] where ??? >>> Deriving >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> >>> Higher-Order Functions * A *higher-order function* takes a function as a parameter. * Defining feature of functional programming * Part of the behavior of a h-o function can be controlled by the caller * Many similar function definitions can be replaced by a single h-o function Examples even :: Int -> Bool map even [1..9] filter even [1..9] filter :: (a -> Bool) -> [a] -> [a] * type of map map :: ??? >>> Derive a h-o function from similar function definitions sum :: [Int] -> Int product :: [Int] -> Int and :: [Bool] -> Bool concat :: [[a]] -> [a] * what's common? >>> Quiz foldr (:) [] xs foldr (:) ys xs let snoc x xs = xs ++ [x] in foldr snoc [] xs >>> Lambda Expressions * users of h-o function often define single-use functions * they can be expressed with a *lambda expression* without naming them foldr (\ x xs -> xs ++ [x]) [] xs * the argument is the same function as snoc, but without naming it >>> Unlines unlines :: [String] -> String unlines ["xxx", "yyy", "zzz"] == "xxx\nyyy\nzzz\n" >>> takeLine takeLine :: String -> String takeLine "xxx\nzzz" == "xxx" >>> Generalizing takeLine to takeWhile takeWhile :: ??? >>> Operator sections add1 = \x -> 1+x add1' = (1+) add1'' = (+1) map (+1) ... filter (<0) ... takeWhile (/='\n') ... >>> Defining lines -- recall takeWhile :: (a -> Bool) -> [a] -> [a] dropWhile :: (a -> Bool) -> [a] -> [a] -- break input string at each '\n' -- generally: break a list into segments of elements with a common property lines :: String -> [String] >>> Properties * Wanted: a property that connects takeWhile and dropWhile prop_takeWhile_dropWhile = undefined >>> Generalized lines segments :: (a -> Bool) -> [a] -> [[a]] >>> Comma Separated Values Many programs store information in CSV format: 1,morning,8:45,42.3,5 -- commaSep "1,morning,8:45,42.3,5" == ["1", "morning", "8:45", "5"] commaSep :: String -> [String] >>> words: split a string into words * words are separated by one or more white space characters mywords :: String -> [String] mywords = undefined >>> Partial Applications * Functions need not be applied to all arguments * ok to apply to fewer arguments (a *partial application*) * the resulting value takes the remaining arguments mysum = undefined mysum [1,2,3] >>> Typing partial applications f :: Int -> Bool -> String -> Bool * bracketing of function types and function application >>> Example: count words Convert an input string into a string that lists the words from the input in alphabetical order along with a count of how many times each word occurred. Example * input: "hello world hello dolly" * output "dolly: 1\nhello: 2\nworld: 1\n" ** groupBy ** map is a functor >>> Wrap up: Higher-Order Functions * enable naming of common code patterns -> avoid repetition * generalizing: abstract the differences between uses of the pattern -> high flexibility * lambda expressions, sections, partial applications, function composition enable the creation of functions without requiring a separate definition * many standard patterns available in Prelude ** many of these are easy to reinvent -> not necessary to memorize ** however, studying them gives useful inspiration for problem solving -> speeds up programming "standing on the shoulders of giants"