>>> 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 a)
* 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
alist = [("xxx", 1), ("zzz", 2)]
lookup :: String -> [(String, Int)] -> Maybe Int
lookup "xxx" alist == Just 1
lookup "zzz" alist == Just 2
lookup "bbb" alist == Nothing
* Maybe:
data Maybe a = Nothing | Just a -- Defined in `GHC.Types'
* Either
data Either a b = Left a | Right b -- Defined in `GHC.Types'
f_either :: Int -> Either Int Char
f_either n = if even n then n `div` 2 else char n
* Ordering: expresses the result of a comparison
data Ordering = LT | EQ | GT -- Defined in `GHC.Types'
>>> 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 (from Prelude):
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
x /= y = not (x == y) -- default declaration
-- 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, Ord b) => Ord (a, b) where
(x1, x2) < (y1, y2) = x1 < x2 || x1 == x2 && y1 < y2
instance Ord a => Ord [a] where
[] < [] = False
[] < ys = True
(x:xs) < (y:ys) = x < y || x == y && xs < ys
>>> Deriving
instance Ord Rank where
(<) = rankBeats
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
>>> 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 :: (a -> b) -> [a] -> [b ]
map even [1..9]
-- map :: (Int -> Bool) -> [Int] -> [Bool]
map ord "hello"
-- map :: (Char -> Int) -> [Char] -> [Int]
map not [False, True]
-- map :: (Bool -> Bool) -> [Bool] -> [Bool]
>>> Derive a h-o function from similar function definitions
sum :: [Int] -> Int
product :: [Int] -> Int
and :: [Bool] -> Bool
concat :: [[a]] -> [a]
* what's common?
-- sum [1,2,3] == 6
sum [] = 0
sum (x:xs) = x + sum xs
-- product [1..5] == 120
product [] = 1
product (x:xs) = x * product xs
-- and [True, True] == True
and [] = True
and (x:xs) = x && and xs
-- common pattern: foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr op n [] = n
foldr op n (x:xs) = x `op` foldr op n xs
sum xs = foldr (+) 0 xs
product xs = foldr (*) 1 xs
and xs = foldr (&&) True xs
>>> Intuition for foldr:
r = foldr op n xs
xs == x1 : (x2 : (x3 : .... : (xn : [])...))
r == x1 `op` (x2 `op` (x3 `op` .... `op` (xn `op` n)...))
-- action of foldr:
-- replace (:) by `op`
-- replace [] by n
>>> Quiz
foldr (:) [] xs == xs
foldr (:) ys xs == xs ++ ys
let snoc x xs = xs ++ [x] in
foldr snoc [] xs
== reverse 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"
-- foldr :: (a -> b -> b) -> b -> [a] -> b
unlines xs = foldr (\ s r -> s ++ "\n" ++ r ) [] xs
unlines xs = foldr f n xs
where f s r = s ++ "\n" ++ r
n = []
>>> takeLine
takeLine :: String -> String
takeLine "xxx\nzzz" == "xxx"
takeLine [] = []
takeLine (x:xs) | x /= '\n' = x : takeLine xs
| otherwise = []
>>> Generalizing takeLine to takeWhile
takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p [] = []
takeWhile p (x:xs) | p x = x : takeWhile p xs
| otherwise = []
takeLine == takeWhile (\ x -> x /= '\n')
== takeWhile (/= '\n')
>>> 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]
-- takeWhile p xs ++ dropWhile p xs == xs
-- break input string at each '\n'
-- generally:
-- break a list into segments of elements with a common property
lines :: String -> [String]
lines = undefined
>>> Properties
* Wanted: a property that connects takeWhile and dropWhile
prop_takeWhile_dropWhile = undefined
>>> Generalized lines
segments :: (a -> Bool) -> [a] -> [[a]]
segments = undefined
>>> 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
f 1 :: Bool -> String -> Bool
f 1 True :: String -> Bool
f 1 True "ende" :: Bool
f :: Int -> (Bool -> (String -> Bool))
(((f 1) True) "ende")
* bracketing of function types and function application
-- function application vs function composition
f :: A -> B
x :: A
f x :: B --- application
g :: B -> C
g . f :: A -> C
-- definition of function composition
(.) :: (b -> c) -> (a -> b) -> (a -> c)
g . f = \a -> g (f a)
-- definition of infix function application
($) :: (a -> b) -> a -> b
f $ a = f a
>>> 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:
** map f . map g = map (f . g)
** (f . (\x -> e)) == (\x -> f e)
words "hello world hello dolly"
== ["hello","world","hello","dolly"]
sort ["hello","world","hello","dolly"]
== ["dolly","hello","hello","world"]
groupBy (==) ["dolly","hello","hello","world"]
== [["dolly"],["hello","hello"],["world"]]
map (\xs -> (head xs, length xs)) [["dolly"],["hello","hello"],["world"]]
== [("dolly",1),("hello",2),("world",1)]
map (\(xs, n) -> xs ++ ": " ++ show n) [("dolly",1),("hello",2),("world",1)]
== ["dolly: 1","hello: 2","world: 1"]
unlines ["dolly: 1","hello: 2","world: 1"]
== "dolly: 1\nhello: 2\nworld: 1\n"
>>> simplification using "map is a functor"
map (\(xs, n) -> xs ++ ": " ++ show n) .
map (\xs -> (head xs, length xs))
==
map ((\(xs, n) -> xs ++ ": " ++ show n) . (\xs -> (head xs, length xs)))
==
map (\xs -> (\(xs, n) -> xs ++ ": " ++ show n) $ (head xs, length xs))
==
map (\xs -> (head xs) ++ ": " ++ show (length xs))
>>> 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"