>>> 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"