diff --git a/part1.html b/part1.html index 89a74d6..49670b3 100644 --- a/part1.html +++ b/part1.html @@ -1042,8 +1042,10 @@ Because `...` is used for delimiting strings.

1.13 Working on the exercises

The course materials, including exercises, are available in a Git repository on GitHub at https://github.com/moocfi/haskell-mooc.

If you’re not familiar with Git, this might help.

-

Once you’ve cloned the haskell-mooc repository, go into the exercises directory. You’ll find the first set of exercises in the file Set1.hs. This is a Haskell source file that you should edit according to the instructions. You can check your answers with:

-
stack runhaskell Set1Test.hs
+

Once you’ve cloned the haskell-mooc repository, go into the exercises directory. To download GHC and various libraries needed by this course, run this command in your terminal (this might take a while):

+
stack build
+

You’ll find the first set of exercises in the file Set1.hs. This is a Haskell source file that you should edit according to the instructions in the file itself. You can check your answers by running this command in your terminal:

+
stack runhaskell Set1Test.hs

The output of the tests looks something like this:

===== EXERCISE 1
 +++++ Pass
@@ -1118,114 +1120,114 @@ cabal v2-exec runhaskell Set1Test.hs

2.1 Guards

Before we move on to new types, let’s go over one more piece of Haskell syntax.

The if then else is often a bit cumbersome, especially when you have multiple cases. An easier alternative is Haskell’s conditional definition or guarded definition. This is a bit like pattern matching in that you have multiple equations, but you can have arbitrary code deciding which equation to use. Guarded definitions look like this:

-
f x y z
-  | condition1 = something
-  | condition2 = other
-  | otherwise  = somethingother
+
f x y z
+  | condition1 = something
+  | condition2 = other
+  | otherwise  = somethingother

A condition can be any expression of type Bool. The first condition that evaluates to True is chosen. The word otherwise is just an alias for True. It is used to mark the default case.

2.1.1 Examples

Here are some examples of using guards. First off, we have a function that describes the given number. Note how it is important to have the "Two" case before the "Even" case.

-
describe :: Int -> String
-describe n
-  | n==2      = "Two"
-  | even n    = "Even"
-  | n==3      = "Three"
-  | n>100     = "Big!!"
-  | otherwise = "The number "++show n
+
describe :: Int -> String
+describe n
+  | n==2      = "Two"
+  | even n    = "Even"
+  | n==3      = "Three"
+  | n>100     = "Big!!"
+  | otherwise = "The number "++show n

Here is factorial, implemented with guards instead of pattern matching. Unlike the pattern-matching version, this one doesn’t loop forever with negative inputs.

-
factorial n
-  | n<0       = -1
-  | n==0      = 1
-  | otherwise = n * factorial (n-1)
+
factorial n
+  | n<0       = -1
+  | n==0      = 1
+  | otherwise = n * factorial (n-1)

You can even combine guards with pattern matching. Here’s the implementation of a simple age guessing game:

-
guessAge :: String -> Int -> String
-guessAge "Griselda" age
-    | age < 47 = "Too low!"
-    | age > 47 = "Too high!"
-    | otherwise = "Correct!"
-guessAge "Hansel" age
-    | age < 12 = "Too low!"
-    | age > 12 = "Too high!"
-    | otherwise = "Correct!"
-guessAge name age = "Wrong name!"
-
Prelude> guessAge "Griselda" 30
-"Too low!"
-Prelude> guessAge "Griselda" 60
-"Too high!"
-Prelude> guessAge "Griselda" 47
-"Correct!"
-Prelude> guessAge "Bob" 30
-"Wrong name!"
-Prelude> guessAge "Hansel" 10
-"Too low!"
+
guessAge :: String -> Int -> String
+guessAge "Griselda" age
+    | age < 47 = "Too low!"
+    | age > 47 = "Too high!"
+    | otherwise = "Correct!"
+guessAge "Hansel" age
+    | age < 12 = "Too low!"
+    | age > 12 = "Too high!"
+    | otherwise = "Correct!"
+guessAge name age = "Wrong name!"
+
Prelude> guessAge "Griselda" 30
+"Too low!"
+Prelude> guessAge "Griselda" 60
+"Too high!"
+Prelude> guessAge "Griselda" 47
+"Correct!"
+Prelude> guessAge "Bob" 30
+"Wrong name!"
+Prelude> guessAge "Hansel" 10
+"Too low!"

2.2 Lists

So far we’ve always worked with single values like number or booleans. Strings contain multiple characters, but still in some sense a string is just one piece of information. In order to be able to do actual programming, we need to handle variable numbers of items. For this we need data structures.

The basic datastructure in Haskell is the list. Lists are used to store multiple values of the same type (in other words, Haskell lists are homogeneous). This is what a list literal looks like:

-
[0,3,4,1+1]
+
[0,3,4,1+1]

A list type is written as [Element], where Element is the type of the lists elements. Here are some more list expressions and their types:

-
[True,True,False] :: [Bool]
-["Moi","Hei"] :: [String]
-[] :: [a]                   -- more about this later
-[[1,2],[3,4]] :: [[Int]]    -- a list of lists
-[1..7] :: [Int]             -- range syntax, value [1,2,3,4,5,6,7]
+
[True,True,False] :: [Bool]
+["Moi","Hei"] :: [String]
+[] :: [a]                   -- more about this later
+[[1,2],[3,4]] :: [[Int]]    -- a list of lists
+[1..7] :: [Int]             -- range syntax, value [1,2,3,4,5,6,7]

Haskell lists are implemented as singly-linked lists. We’ll return to this later.

2.2.1 List operations

The Haskell standard library comes with lots of functions that operate on lists. Here are some of the most important ones, together with their types. We’ll get back to what [a] actually means in a second, but for now you can imagine it means “any list”.

-
head :: [a] -> a            -- returns the first element
-tail :: [a] -> [a]          -- returns everything except the first element
-take :: Int -> [a] -> [a]   -- returns the n first elements
-drop :: Int -> [a] -> [a]   -- returns everything except the n first elements
-(++) :: [a] -> [a] -> [a]   -- lists are catenated with the ++ operator
-(!!) :: [a] -> Int -> a     -- lists are indexed with the !! operator
-null :: [a] -> Bool         -- is this list empty?
-length :: [a] -> Int        -- the length of a list
+
head :: [a] -> a            -- returns the first element
+tail :: [a] -> [a]          -- returns everything except the first element
+take :: Int -> [a] -> [a]   -- returns the n first elements
+drop :: Int -> [a] -> [a]   -- returns everything except the n first elements
+(++) :: [a] -> [a] -> [a]   -- lists are catenated with the ++ operator
+(!!) :: [a] -> Int -> a     -- lists are indexed with the !! operator
+null :: [a] -> Bool         -- is this list empty?
+length :: [a] -> Int        -- the length of a list

Sidenote: the last two operations (null and length) actually have more generic types, but here I’m pretending that you can only use them on lists.

Lists can be compared with the familiar == operator.

Do you remember this from our first GHCi session?

-
Prelude> :t "asdf"
-"asdf" :: [Char]
+
Prelude> :t "asdf"
+"asdf" :: [Char]

This means that String is just an alias for [Char], which means string is a list of characters. This means you can use all list operations on strings!

Some list operations come from the module Data.List. You can import a module in code or in GHCi with the import Data.List syntax. One example is the sort function which sorts a list:

-
Prelude> import Data.List
-Prelude Data.List> sort [1,0,5,3]
-[0,1,3,5]
+
Prelude> import Data.List
+Prelude Data.List> sort [1,0,5,3]
+[0,1,3,5]

Note how the set of imported modules shows up in the GHCi prompt.

2.2.2 Examples

Here are some examples of working with lists. In this case, instead of showing you output from GHCi, I merely use ==> to show what an expression evaluates to.

Indexing a list:

-
[7,10,4,5] !! 2
-  ==> 4
+
[7,10,4,5] !! 2
+  ==> 4

Defining a function that discards the 3rd and 4th elements of a list using take and drop:

-
f xs = take 2 xs ++ drop 4 xs
-
f [1,2,3,4,5,6]  ==>  [1,2,5,6]
-f [1,2,3]        ==>  [1,2]
+
f xs = take 2 xs ++ drop 4 xs
+
f [1,2,3,4,5,6]  ==>  [1,2,5,6]
+f [1,2,3]        ==>  [1,2]

Rotating a list by taking the first element and moving it to the end:

-
g xs = tail xs ++ [head xs]
-
g [1,2,3]      ==>  [2,3,1]
-g (g [1,2,3])  ==>  [3,1,2]
+
g xs = tail xs ++ [head xs]
+
g [1,2,3]      ==>  [2,3,1]
+g (g [1,2,3])  ==>  [3,1,2]

Here’s an example of the range syntax:

-
reverse [1..4] ==> [4,3,2,1]
+
reverse [1..4] ==> [4,3,2,1]

2.3 A word about immutability

Because Haskell is pure, it also means that functions can’t mutate (change) their inputs. Mutation is a side effect, and Haskell functions are only allowed output via their return value. This means that Haskell list functions always return a new list. In practice:

-
Prelude> let list = [1,2,3,4]
-Prelude> reverse list
-[4,3,2,1]
-Prelude> list
-[1,2,3,4]
-Prelude> drop 2 list
-[3,4]
-Prelude> list
-[1,2,3,4]
+
Prelude> let list = [1,2,3,4]
+Prelude> reverse list
+[4,3,2,1]
+Prelude> list
+[1,2,3,4]
+Prelude> drop 2 list
+[3,4]
+Prelude> list
+[1,2,3,4]

This might seem very inefficient but it turns out it can be both performant and quite useful. We’ll get back to how Haskell datastructures work in a later lecture.

2.4 A word about type inference and polymorphism

So what does a type like head :: [a] -> a mean? It means given a list that contains elements of any type a, the return value will be of the same type a.

In this type, a is a type variable. Type variables are types that start with a small letter, e.g. a, b, thisIsATypeVariable. A type variable means a type that is not yet known, or in other words a type that could be anything. Type variables can turn into concrete types (e.g. Bool) by the process of type inference (also called unification).

Let’s have a look at some examples. If we apply head to a list of booleans, type inference will compare the type of head’s argument, [a], with the type of the actual argument, [Bool] and deduce that a must be Bool. This means that the return type of head will in this case also be Bool.

-
head :: [a] -> a
-head [True,False] :: Bool
+
head :: [a] -> a
+head [True,False] :: Bool

The function tail takes a list, and returns a list of the same type. If we apply tail to a list of booleans, the return value will also be a list of booleans.

-
tail :: [a] -> [a]
-tail [True,False] :: [Bool]
+
tail :: [a] -> [a]
+tail [True,False] :: [Bool]

If types don’t match, we get a type error. Consider the operator ++ which takes two lists of the same type, as we can see from its type [a] -> [a] -> [a]. If we try to apply ++ to a list of booleans and a list of characters we get an error. This what happens in GHCi:

Prelude> [True,False] ++ "Moi"
 
@@ -1236,14 +1238,14 @@ cabal v2-exec runhaskell Set1Test.hs
In the second argument of `(++)', namely `"Moi"' In the expression: [True, False] ++ "Moi"

Type inference is really powerful. It uses the simple process of unification to get us types for practically any Haskell expression. Consider these two functions:

-
f xs ys = [head xs, head ys]
-g zs = f "Moi" zs
+
f xs ys = [head xs, head ys]
+g zs = f "Moi" zs

We can ask GHCi for their types, and we will see that type inference has figured out that the two arguments to f must have the same type, since their heads get put into the same list.

-
Prelude> :t f
-f :: [a] -> [a] -> [a]
+
Prelude> :t f
+f :: [a] -> [a] -> [a]

The function g, which fixed one of the arguments of f to a string (i.e. [Char]) gets a narrower type. Type inference has decided that the argument zs to g must also have type [Char], since otherwise the type of f would not match the call to f.

-
Prelude> :t g
-g :: [Char] -> [Char]
+
Prelude> :t g
+g :: [Char] -> [Char]

2.4.1 Sidenote: some terminology

In a type like [Char] we call Char a type parameter. A type like the list type that needs a type parameter is called a parameterized type.

The fact that a function like head can be used with many different types of arguments is called polymorphism. The head function is said to be polymorphic. There are many forms of polymorphism, and this Haskell form that uses type variables is called parametric polymorphism.

@@ -1283,18 +1285,18 @@ cabal v2-exec runhaskell Set1Test.hs

You can think of Maybe a as being a bit like [a] except there can only be 0 or 1 elements, not more. Alternatively, you can think of Maybe a introducing a null value to the type a. If you’re familiar with Java, Maybe Integer is the Haskell equivalent of Java’s Optional<Integer>.

You use a Maybe value by pattern matching on it. Some more examples:

-
intOrZero :: Maybe Int -> Int
-intOrZero Nothing = 0
-intOrZero (Just i) = i
-
-safeHead :: [a] -> Maybe a
-safeHead [] = Nothing
-safeHead (x:_) = Just x
-
-headOrZero :: [Int] -> Int
-headOrZero xs = intOrZero (safeHead xs)
-
headOrZero []  ==> intOrZero (safeHead [])  ==> intOrZero Nothing  ==> 0
-headOrZero [1] ==> intOrZero (safeHead [1]) ==> intOrZero (Just 1) ==> 1
+
intOrZero :: Maybe Int -> Int
+intOrZero Nothing = 0
+intOrZero (Just i) = i
+
+safeHead :: [a] -> Maybe a
+safeHead [] = Nothing
+safeHead (x:_) = Just x
+
+headOrZero :: [Int] -> Int
+headOrZero xs = intOrZero (safeHead xs)
+
headOrZero []  ==> intOrZero (safeHead [])  ==> intOrZero Nothing  ==> 0
+headOrZero [1] ==> intOrZero (safeHead [1]) ==> intOrZero (Just 1) ==> 1

2.6 Sidenote: Constructors

As you can see above, we can pattern match on the constructors of Maybe: Just and Nothing. We’ll get back to what constructors mean later. For now it’s enough to note that constructors are special values that start with a capital letter that you can pattern match on.

Other constructors that we’ve already seen include the constructors of Bool – True and False. We’ll introduce the constructors of the list type on the next lecture.

@@ -1326,15 +1328,15 @@ Just :: a -> Maybe a

Here’s a simple example: a readInt function that only knows a couple of numbers and returns a descriptive error for the rest. Note the Haskell convention of using Left for errors and Right for success.

-
readInt :: String -> Either String Int
-readInt "0" = Right 0
-readInt "1" = Right 1
-readInt s = Left ("Unsupported string: " ++ s)
+
readInt :: String -> Either String Int
+readInt "0" = Right 0
+readInt "1" = Right 1
+readInt s = Left ("Unsupported string: " ++ s)

Sidenote: the constructors of Either are called Left and Right because they refer to the left and right type arguments of Either. Note how in Either a b, a is the left argument and b is the right argument. Thus Left contains a value of type a and likewise Right of type b. The convention of using Right for sucess is probably simply because right also means correct. No offense is intended to left-handed people.

Here’s another example: pattern matching an Either.

-
iWantAString :: Either Int String -> String
-iWantAString (Right str)   = str
-iWantAString (Left number) = show number
+
iWantAString :: Either Int String -> String
+iWantAString (Right str)   = str
+iWantAString (Left number) = show number

As you recall, Haskell lists can only contain elements of the same type. You can’t have a value like [1,"foo",2]. However, you can use a type like Either to represent lists that can contain two different types of values. For example we could track the number of people on a lecture, with a possibility of adding an explanation if a value is missing:

lectureParticipants :: [Either String Int]
 lectureParticipants = [Right 10, Right 13, Left "easter vacation", Right 17, Left "lecturer was sick", Right 3]
@@ -1414,105 +1416,105 @@ Because Nothing is a constructor

3.1 Functional programming, at last

Now with lists and polymorphism in our toolbox, we can finally start to look at functional programming.

In Haskell a function is a value, just like a number or a list is. Functions can be passed as parameters to other functions. Here’s a toy example. The function applyTo1 takes a function of type Int->Int, applies it to the number 1, and returns the result.

-
applyTo1 :: (Int -> Int) -> Int
-applyTo1 f = f 1
+
applyTo1 :: (Int -> Int) -> Int
+applyTo1 f = f 1

Let’s define a simple function of type Int->Int and see applyTo1 in action.

-
addThree :: Int -> Int
-addThree x = x + 3
-
applyTo1 addThree
-  ==> addThree 1
-  ==> 1 + 3
-  ==> 4
+
addThree :: Int -> Int
+addThree x = x + 3
+
applyTo1 addThree
+  ==> addThree 1
+  ==> 1 + 3
+  ==> 4

Let’s go back to the type annotation for applyTo1.

-
applyTo1 :: (Int -> Int) -> Int
+
applyTo1 :: (Int -> Int) -> Int

The parentheses are needed because the type Int -> Int -> Int would be the type of a function taking two Int arguments. More on this later.

Let’s look at a slightly more interesting example. This time we’ll implement a polymorphic function doTwice. Note how we can use it with various types of values and functions.

-
doTwice :: (a -> a) -> a -> a
-doTwice f x = f (f x)
-
doTwice addThree 1
-  ==> addThree (addThree 1)
-  ==> 7
-doTwice tail "abcd"
-  ==> tail (tail "abcd")
-  ==> "cd"
-
makeCool :: String -> String
-makeCool str = "WOW " ++ str ++ "!"
-
doTwice makeCool "Haskell"
-  ==> "WOW WOW Haskell!!"
+
doTwice :: (a -> a) -> a -> a
+doTwice f x = f (f x)
+
doTwice addThree 1
+  ==> addThree (addThree 1)
+  ==> 7
+doTwice tail "abcd"
+  ==> tail (tail "abcd")
+  ==> "cd"
+
makeCool :: String -> String
+makeCool str = "WOW " ++ str ++ "!"
+
doTwice makeCool "Haskell"
+  ==> "WOW WOW Haskell!!"

3.1.1 Functional programming on lists

That was a bit boring. Luckily there are many useful list functions that take functions as arguments. By the way, functions that take functions as arguments (or return functions) are often called higher-order functions.

The most famous of these list-processing higher-order functions is map. It gives you a new list by applying the given function to all elements of a list.

-
map :: (a -> b) -> [a] -> [b]
-
map addThree [1,2,3]
-  ==> [4,5,6]
+
map :: (a -> b) -> [a] -> [b]
+
map addThree [1,2,3]
+  ==> [4,5,6]

The partner in crime for map is filter. Instead of transforming all elements of a list, filter drops some elements of a list and keeps others. In other words, filter selects the elements from a list that fulfill a condition.

-
filter :: (a -> Bool) -> [a] -> [a]
+
filter :: (a -> Bool) -> [a] -> [a]

Here’s an example: selecting the positive elements from a list

-
positive :: Int -> Bool
-positive x = x>0
-
filter positive [0,1,-1,3,-3]
-  ==> [1,3]
+
positive :: Int -> Bool
+positive x = x>0
+
filter positive [0,1,-1,3,-3]
+  ==> [1,3]

Note how both the type signatures of map and filter use polymorphism. They work on all kinds of lists. The type of map even uses two type parameters! Here are some examples of type inference using map and filter.

-
onlyPositive xs = filter positive xs
-mapBooleans f = map f [False,True]
-
Prelude> :t onlyPositive
-onlyPositive :: [Int] -> [Int]
-Prelude> :t mapBooleans
-mapBooleans :: (Bool -> b) -> [b]
-Prelude> :t mapBooleans not
-mapBooleans not :: [Bool]
+
onlyPositive xs = filter positive xs
+mapBooleans f = map f [False,True]
+
Prelude> :t onlyPositive
+onlyPositive :: [Int] -> [Int]
+Prelude> :t mapBooleans
+mapBooleans :: (Bool -> b) -> [b]
+Prelude> :t mapBooleans not
+mapBooleans not :: [Bool]

One more thing: remember how constructors were just functions? That means you can pass them as arguments to other functions!

-
wrapJust xs = map Just xs
-
Prelude> :t wrapJust
-wrapJust :: [a] -> [Maybe a]
-Prelude> wrapJust [1,2,3]
-[Just 1,Just 2,Just 3]
+
wrapJust xs = map Just xs
+
Prelude> :t wrapJust
+wrapJust :: [a] -> [Maybe a]
+Prelude> wrapJust [1,2,3]
+[Just 1,Just 2,Just 3]

3.1.2 Examples of functional programming on lists

How many “palindrome numbers” are between 1 and n?

-
-- a predicate that checks if a string is a palindrome
-palindrome :: String -> Bool
-palindrome str = str == reverse str
-
--- palindromes n takes all numbers from 1 to n, converts them to strings using show, and keeps only palindromes
-palindromes :: Int -> [String]
-palindromes n = filter palindrome (map show [1..n])
-
palindrome "1331" ==> True
-palindromes 150 ==>
-  ["1","2","3","4","5","6","7","8","9",
-   "11","22","33","44","55","66","77","88","99",
-   "101","111","121","131","141"]
-length (palindromes 9999) ==> 198
+
-- a predicate that checks if a string is a palindrome
+palindrome :: String -> Bool
+palindrome str = str == reverse str
+
+-- palindromes n takes all numbers from 1 to n, converts them to strings using show, and keeps only palindromes
+palindromes :: Int -> [String]
+palindromes n = filter palindrome (map show [1..n])
+
palindrome "1331" ==> True
+palindromes 150 ==>
+  ["1","2","3","4","5","6","7","8","9",
+   "11","22","33","44","55","66","77","88","99",
+   "101","111","121","131","141"]
+length (palindromes 9999) ==> 198

How many words in a string start with “a”? This uses the function words from the module Data.List that splits a string into words.

-
countAWords :: String -> Int
-countAWords string = length (filter startsWithA (words string))
-  where startsWithA s = head s == 'a'
-
countAWords "does anyone want an apple?"
-  ==> 3
+
countAWords :: String -> Int
+countAWords string = length (filter startsWithA (words string))
+  where startsWithA s = head s == 'a'
+
countAWords "does anyone want an apple?"
+  ==> 3

The function tails from Data.List returns the list of all suffixes (“tails”) of a list. We can use tails for many string processing tasks. Here’s how tails works:

-
tails "echo"
-  ==> ["echo","cho","ho","o",""]
+
tails "echo"
+  ==> ["echo","cho","ho","o",""]

Here’s an example where we find what characters come after a given character in a string. First of all, we use tails, map and take to get all substrings of a certain length:

-
substringsOfLength :: Int -> String -> [String]
-substringsOfLength n string = map shorten (tails string)
-  where shorten s = take n s
-
substringsOfLength 3 "hello"
-  ==> ["hel","ell","llo","lo","o",""]
+
substringsOfLength :: Int -> String -> [String]
+substringsOfLength n string = map shorten (tails string)
+  where shorten s = take n s
+
substringsOfLength 3 "hello"
+  ==> ["hel","ell","llo","lo","o",""]

There’s some shorter substrings left at the end (can you see why?), but they’re fine for our purposes right now. Now that we have substringsOfLenght, we can implement the function whatFollows c k s that finds all the occurrences of the character c in the string s, and outputs the k letters that come after these occurrences.

-
whatFollows :: Char -> Int -> String -> [String]
-whatFollows c k string = map tail (filter match (substringsOfLength (k+1) string))
-  where match sub = take 1 sub == [c]
-
whatFollows 'a' 2 "abracadabra"
-  ==> ["br","ca","da","br",""]
+
whatFollows :: Char -> Int -> String -> [String]
+whatFollows c k string = map tail (filter match (substringsOfLength (k+1) string))
+  where match sub = take 1 sub == [c]
+
whatFollows 'a' 2 "abracadabra"
+  ==> ["br","ca","da","br",""]

3.2 Partial application

When using higher-order functions you can find yourself defining lots of small helper functions, like addThree or process in the previous examples. This is a bit of a chore in the long run, but luckily Haskell’s functions behave a bit weirdly…

Let’s start in GHCi:

-
Prelude> let add a b = a+b
-Prelude> add 1 5
-6
-Prelude> let addThree = add 3
-6
-Prelude> addThree 2
-5
+
Prelude> let add a b = a+b
+Prelude> add 1 5
+6
+Prelude> let addThree = add 3
+6
+Prelude> addThree 2
+5

So, we’ve defined add, a function of two arguments, and only given it one argument. The result is not a type error but a new function. The new function just stores (or remembers) the given argument, waits for another argument, and then gives both to add.

Prelude> map addThree [1,2,3]
 [4,5,6]
@@ -1520,189 +1522,189 @@ Prelude> map (add 3) [1,2,3]
 [4,5,6]

Here we can see that we don’t even need to give a name to the function returned by add 3. We can just use it anywhere where a function of one argument is expected.

This is called partial application. All functions in Haskell behave like this. Let’s have a closer look. Here’s a function that takes many arguments.

-
between :: Integer -> Integer -> Integer -> Bool
-between lo high x = x < high && x > lo
-
Prelude> between 3 7 5
-True
-Prelude> between 3 6 8
-False
-

We can give inOrder less arguments and get back new functions, just like we saw with add:

-
Prelude> (inOrder 1 5) 2
+
+
+Prelude> between 3 6 8
+False
+

We can give inOrder less arguments and get back new functions, just like we saw with add:

+
Prelude> (inOrder 1 5) 2
+True
+Prelude> let f = inOrder 1 5 in f 2
+True
+Prelude> map (between 1 3) [1,2,3]
+[False,True,False]

Look at the types of partially applying between. They behave neatly, with arguments disappearing one by one from the type as values are added to the expression.

-
Prelude> :t between
-between :: Integer -> Integer -> Integer -> Bool
-Prelude> :t between 1
-between 1 :: Integer -> Integer -> Bool
-Prelude> :t between 1 2
-between 1 2 :: Integer -> Bool
-Prelude> :t between 1 2 3
-between 1 2 3 :: Bool
+
Prelude> :t between
+between :: Integer -> Integer -> Integer -> Bool
+Prelude> :t between 1
+between 1 :: Integer -> Integer -> Bool
+Prelude> :t between 1 2
+between 1 2 :: Integer -> Bool
+Prelude> :t between 1 2 3
+between 1 2 3 :: Bool

Actually, when we write a type like Integer -> Integer -> Integer -> Bool, it means Integer -> (Integer -> (Integer -> Bool)). That is, a multi-argument function is just a function that returns a function. Similarly, an expression like between 1 2 3 is the same as ((between 1) 2) 3, so passing multiple arguments to a function happens via multiple single-argument calls. Representing multi-argument functions like this is called currying (after the logician Haskell Curry). Currying is what makes partial application possible.

Here’s another example of using partial application with map:

-
map (drop 1) ["Hello","World!"]
-  ==> ["ello","orld!"]
+
map (drop 1) ["Hello","World!"]
+  ==> ["ello","orld!"]

In addition to normal functions, partial application also works with operators. With operators you can choose whether you apply the left or the right argument. (Partially applied operators are also called sections or operator sections). Some examples:

-
Prelude> map (*2) [1,2,3]
-[2,4,6]
-Prelude> map (2*) [1,2,3]
-[2,4,6]
-Prelude> map (1/) [1,2,3,4,5]
-[1.0,0.5,0.3333333333333333,0.25,0.2]
+
Prelude> map (*2) [1,2,3]
+[2,4,6]
+Prelude> map (2*) [1,2,3]
+[2,4,6]
+Prelude> map (1/) [1,2,3,4,5]
+[1.0,0.5,0.3333333333333333,0.25,0.2]

3.3 Prefix and Infix Notations

An infix operator can be converted into a prefix function by adding parentheses around it. For instance,

-
(+) 1 2 ==> 1 + 2 ==> 3
+
(+) 1 2 ==> 1 + 2 ==> 3

This is useful especially when an operator needs to be passed as an argument to another function.

As an example, the function zipWith takes two lists, a binary function, and joins the lists using the function. We can use zipWith (+) to sum two lists, element-by-element:

-
Prelude> :t zipWith
-zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
-Prelude> zipWith (+) [0,2,5] [1,3,3]
-[1,5,8]
+
Prelude> :t zipWith
+zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
+Prelude> zipWith (+) [0,2,5] [1,3,3]
+[1,5,8]

Without the ability to turn an operator into a function, we’d have to use a helper function – such as add above.

Note that omitting the parentheses leads into a type error:

-
Prelude> zipWith + [0,2,5,3] [1,3,3]
-
-<interactive>:1:11: error:
-    • Couldn't match expected type ‘[Integer]
-                                    -> (a -> b -> c) -> [a] -> [b] -> [c]’
-                  with actual type ‘[Integer]’
-    • The function ‘[0, 2, 5, 3]’ is applied to one argument,
-      but its type ‘[Integer]’ has none
-      In the second argument of ‘(+)’, namely ‘[0, 2, 5, 3] [1, 3, 3]’
-      In the expression: zipWith + [0, 2, 5, 3] [1, 3, 3]
-    • Relevant bindings include
-        it :: (a -> b -> c) -> [a] -> [b] -> [c]
-          (bound at <interactive>:1:1)
+
Prelude> zipWith + [0,2,5,3] [1,3,3]
+
+<interactive>:1:11: error:
+    • Couldn't match expected type ‘[Integer]
+                                    -> (a -> b -> c) -> [a] -> [b] -> [c]’
+                  with actual type ‘[Integer]’
+    • The function ‘[0, 2, 5, 3]’ is applied to one argument,
+      but its type ‘[Integer]’ has none
+      In the second argument of ‘(+)’, namely ‘[0, 2, 5, 3] [1, 3, 3]’
+      In the expression: zipWith + [0, 2, 5, 3] [1, 3, 3]
+    • Relevant bindings include
+        it :: (a -> b -> c) -> [a] -> [b] -> [c]
+          (bound at <interactive>:1:1)

The reason for this weird-looking error is that GHCI got confused and thought that we were somehow trying to add zipWith and [0,2,5,3] [1,3,3] together. Logically, it deduced that [0,2,5,3] must be a function since it’s being applied to [1,3,3] (remember that functions bind tighter than operators).

Unfortunately, error messages can sometimes be obscure, since the compiler cannot always know the “real” cause of the error (which is in this case was omitting the parentheses). Weird error messages are frustrating, but only the programmer knows what was the original intent behind the code.

Another nice feature of Haskell is the syntax for applying a binary function as if it was an infix operator, by surrounding it with backticks (`). For example:

-
6 `div` 2 ==> div 6 2 ==> 3
-(+1) `map` [1,2,3] ==> map (+1) [1,2,3] ==> [2,3,4]
+
6 `div` 2 ==> div 6 2 ==> 3
+(+1) `map` [1,2,3] ==> map (+1) [1,2,3] ==> [2,3,4]

3.4 Lambdas

The last spanner we need in our functional programming toolbox is λ (lambda). Lambda expressions are anonymous functions. Consider a situation where you need a function only once, for example in an expression like

-
let big x = x>7 in filter big [1,10,100]
+
let big x = x>7 in filter big [1,10,100]

A lambda expression allows us to write this directly, without defining a name (big) for the helper function:

-
filter (\x -> x>7) [1,10,100]
+
filter (\x -> x>7) [1,10,100]

Here are some more examples in GHCi:

-
Prelude> (\x -> x*x) 3
-9
-Prelude> (\x -> reverse x == x) "ABBA"
-True
-Prelude> filter (\x -> reverse x == x) ["ABBA","ACDC","otto","lothar","anna"]
-["ABBA","otto","anna"]
-Prelude> (\x y -> x^2+y^2) 2 3           -- multiple arguments
-13
+
Prelude> (\x -> x*x) 3
+9
+Prelude> (\x -> reverse x == x) "ABBA"
+True
+Prelude> filter (\x -> reverse x == x) ["ABBA","ACDC","otto","lothar","anna"]
+["ABBA","otto","anna"]
+Prelude> (\x y -> x^2+y^2) 2 3           -- multiple arguments
+13

By the way, lambda expressions are quite powerful constructs which have a deep theory of their own, known as Lambda calculus. Some even consider purely functional programming languages such as Haskell to be typed extensions of Lambda calculus with extra syntax.

3.5 Sidenote: the . and $ operators

The two most common operators in Haskell codebases are probably . and $. They are useful when writing code that uses higher-order functions. The first of these, the . operator, is the function composition operator. Here’s its type

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

And here’s what it does

(f.g) x ==> f (g x)

You can use function composition to build functions out of other functions, without mentioning any arguments. For example:

-
double x = 2*x
-quadruple = double . double  -- computes 2*(2*x) == 4*x
-f = quadruple . (+1)         -- computes 4*(x+1)
-third = head . tail . tail   -- fetches the third element of a list
+
double x = 2*x
+quadruple = double . double  -- computes 2*(2*x) == 4*x
+f = quadruple . (+1)         -- computes 4*(x+1)
+third = head . tail . tail   -- fetches the third element of a list

We can also reimplement doTwice using (.). Note how we can use doTwice both as applied only to a function, or as applies to a function and a value.

-
doTwice :: (a -> a) -> a -> a
-doTwice f = f . f
-
let ttail = doTwice tail
-in ttail [1,2,3,4]
-  ==> [3,4]
-
-(doTwice tail) [1,2,3,4] ==> [3,4]
-
-doTwice tail [1,2,3,4] ==> [3,4]
+
doTwice :: (a -> a) -> a -> a
+doTwice f = f . f
+
let ttail = doTwice tail
+in ttail [1,2,3,4]
+  ==> [3,4]
+
+(doTwice tail) [1,2,3,4] ==> [3,4]
+
+doTwice tail [1,2,3,4] ==> [3,4]

Often function composition is not used when defining a new function, but instead to avoid defining a helper function. For instance, consider the difference between these two expressions:

-
let notEmpty x = not (null x)
-in filter notEmpty [[1,2,3],[],[4]]
-  ==> [[1,2,3],[4]]
-
filter (not . null) [[1,2,3],[],[4]]
-  ==> [[1,2,3],[4]]
+
let notEmpty x = not (null x)
+in filter notEmpty [[1,2,3],[],[4]]
+  ==> [[1,2,3],[4]]
+
filter (not . null) [[1,2,3],[],[4]]
+  ==> [[1,2,3],[4]]

The other operator, $ is more subtle. Let’s look at its type.

-
($) :: (a -> b) -> a -> b
+
($) :: (a -> b) -> a -> b

It takes a function of type a -> b and a value of type a, and returns a value of type b. In other words, it’s a function application operator. The expression f $ x is the same as f x. This seems pretty useless, but it means that the $ operator can be used to eliminate parentheses! These expressions are the same:

-
head (reverse "abcd")
-head $ reverse "abcd"
+
head (reverse "abcd")
+head $ reverse "abcd"

This isn’t that impressive when it’s used to eliminate one pair of parentheses, but together . and $ can eliminate lots of them! For example, we can rewrite

-
reverse (map head (map reverse (["Haskell","pro"] ++ ["dodo","lyric"])))
+
reverse (map head (map reverse (["Haskell","pro"] ++ ["dodo","lyric"])))

as

-
reverse . map head . map reverse $ ["Haskell","pro"] ++ ["dodo","lyric"]
+
reverse . map head . map reverse $ ["Haskell","pro"] ++ ["dodo","lyric"]

Here’s how the rewritten version evaluates to the original version:

-
     reverse . map head . map reverse $ ["Haskell","pro"] ++ ["dodo","lyric"]
-==> (reverse . map head . map reverse) (["Haskell","pro"] ++ ["dodo","lyric"])
-==> (reverse . map head) (map reverse  (["Haskell","pro"] ++ ["dodo","lyric"]))
-==> (reverse  (map head  (map reverse  (["Haskell","pro"] ++ ["dodo","lyric"]))))
+
     reverse . map head . map reverse $ ["Haskell","pro"] ++ ["dodo","lyric"]
+==> (reverse . map head . map reverse) (["Haskell","pro"] ++ ["dodo","lyric"])
+==> (reverse . map head) (map reverse  (["Haskell","pro"] ++ ["dodo","lyric"]))
+==> (reverse  (map head  (map reverse  (["Haskell","pro"] ++ ["dodo","lyric"]))))

If this seems complicated, don’t worry. You don’t need to use . and $ in your own code until you’re comfortable with them. However, you’ll bump into . and $ when reading Haskell examples and code on the internet, so it’s good to know about them.

3.6 Example: rewriting whatFollows

Now, let’s rewrite the whatFollows example from earlier using the tools we just saw. Here’s the original version:

-
substringsOfLength :: Int -> String -> [String]
-substringsOfLength n string = map shorten (tails string)
-  where shorten s = take n s
-
-whatFollows :: Char -> Int -> String -> [String]
-whatFollows c k string = map tail (filter match (substringsOfLength (k+1) string))
-  where match sub = take 1 sub == [c]
+
substringsOfLength :: Int -> String -> [String]
+substringsOfLength n string = map shorten (tails string)
+  where shorten s = take n s
+
+whatFollows :: Char -> Int -> String -> [String]
+whatFollows c k string = map tail (filter match (substringsOfLength (k+1) string))
+  where match sub = take 1 sub == [c]

To get started, let’s get rid of the helper function substringsOfLength and move all the code to whatFollows:

-
whatFollows c k string = map tail (filter match (map shorten (tails string)))
-  where shorten s = take (k+1) s
-        match sub = take 1 sub == [c]
+
whatFollows c k string = map tail (filter match (map shorten (tails string)))
+  where shorten s = take (k+1) s
+        match sub = take 1 sub == [c]

Now let’s use partial application instead of defining shorten:

-
whatFollows c k string = map tail (filter match (map (take (k+1)) (tails string)))
-  where match sub = take 1 sub == [c]
-

Let’s use . and $ to eliminate some of those parentheses:

-
whatFollows c k string = map tail . filter match . map (take (k+1)) $ tails string
+
+

Let’s use . and $ to eliminate some of those parentheses:

+

We can also replace match with a lambda:

- +

Finally, we don’t need to mention the string parameter at all, since we can just express whatFollows as a composition of map, filter, map and tails:

- +

We can even go a bit further by rewriting the lambda using an operator section

- +

Now what we have left is:

- +

This is a somewhat extreme version of the function, but when used in moderation the techniques shown here can make code easier to read.

3.7 More functional list wrangling examples

Here are some more examples of functional programming with lists. Let’s start by introducing a couple of new list functions:

- - + +

There’s also the function elem, which can be used to check if a list contains an element:

- +

Using these, we can implement a function findSubString that finds the first substring in a string that consist only of the given characters.

- - + +

The function zipWith lets you combine two lists element-by-element:

- - + +

3.8 Lists and recursion

Here’s a new operator, :

- +

The : operator builds a list out of a head and a tail. In other words, x : xs is the same as [x] ++ xs. Why do we need an operator for this?

Actually, : is the constructor for lists: it returns a new linked list node. The other list constructor is [], the empty list. All lists are built using : and []. The familiar [x,y,z] syntax is actually just a nicer way to write x:y:z:[], or even more explicitly, x:(y:(z:[])).

Here’s a picture of how [1,2,3] is structured in memory:

@@ -1715,116 +1717,116 @@ Prelude> map (add 3) [1,2,3] 3 []

3.8.1 Building a list

Using : we can define recursive functions that build lists. For example here’s a function that builds lists like [3,2,1]:

-
descend 0 = []
-descend n = n : descend (n-1)
-
descend 4 ==> [4,3,2,1]
+
descend 0 = []
+descend n = n : descend (n-1)
+
descend 4 ==> [4,3,2,1]

Here’s a function that builds a list by iterating a function n times:

-
iterate f 0 x = [x]
-iterate f n x = x : iterate f (n-1) (f x)
-
iterate (*2) 4 3 ==> [3,6,12,24,48]
-
-let xs = "terve"
-in iterate tail (length xs) xs
-  ==> ["terve","erve","rve","ve","e",""]
+
iterate f 0 x = [x]
+iterate f n x = x : iterate f (n-1) (f x)
+
iterate (*2) 4 3 ==> [3,6,12,24,48]
+
+let xs = "terve"
+in iterate tail (length xs) xs
+  ==> ["terve","erve","rve","ve","e",""]

Here’s a more complicated example: splitting a string into pieces at a given character:

-
split :: Char -> [String] -> [[String]]
-split c [] = []
-split c xs = start : split c (drop 1 rest)
-  where start = takeWhile (/=c) xs
-        rest = dropWhile (/=c) xs
-
split 'x' "fooxxbarxquux"   ==>   ["foo","","bar","quu"]
+
split :: Char -> [String] -> [[String]]
+split c [] = []
+split c xs = start : split c (drop 1 rest)
+  where start = takeWhile (/=c) xs
+        rest = dropWhile (/=c) xs
+
split 'x' "fooxxbarxquux"   ==>   ["foo","","bar","quu"]

3.8.2 Pattern matching for lists

Last lecture, it was said that constructors are things that can be pattern matched on. Above, it was divulged that the constructors for the list type are : and []. We can put one and one together and guess that we can pattern match on : and []. This is true! Here’s how you can define your own versions of head and tail using pattern matching:

-
myhead :: [Int] -> Int
-myhead [] = -1
-myhead (first:rest) = first
-
-mytail :: [Int] -> [Int]
-mytail [] = []
-mytail (first:rest) = rest
+
myhead :: [Int] -> Int
+myhead [] = -1
+myhead (first:rest) = first
+
+mytail :: [Int] -> [Int]
+mytail [] = []
+mytail (first:rest) = rest

You can nest patterns. That is, you can pattern match more than one element from the start of a list. In this example, we use the pattern (a:b:_) which is the same as (a:(b:_)):

-
sumFirstTwo :: [Integer] -> Integer
--- this equation gets used for lists of length at least two
-sumFirstTwo (a:b:_) = a+b
--- this equation gets used for lists of length 0 or 1
-sumFirstTwo _       = 0
-
sumFirstTwo [1]      ==> 0
-sumFirstTwo [1,2]    ==> 3
-sumFirstTwo [1,2,4]  ==> 3
+
sumFirstTwo :: [Integer] -> Integer
+-- this equation gets used for lists of length at least two
+sumFirstTwo (a:b:_) = a+b
+-- this equation gets used for lists of length 0 or 1
+sumFirstTwo _       = 0
+
sumFirstTwo [1]      ==> 0
+sumFirstTwo [1,2]    ==> 3
+sumFirstTwo [1,2,4]  ==> 3

Another way we can nest patterns is pattern matching on the head while pattern matching on a list. For example this function checks if a list starts with 0:

-
startsWithZero :: [Integer] -> Bool
-startsWithZero (0:xs) = True
-startsWithZero (x:xs) = False
-startsWithZero []     = False
+
startsWithZero :: [Integer] -> Bool
+startsWithZero (0:xs) = True
+startsWithZero (x:xs) = False
+startsWithZero []     = False

3.8.3 Consuming a list

Using pattern matching and recursion, we can recursively process a whole list. Here’s how you sum all the numbers in a list:

-
sumNumbers :: [Int] -> Int
-sumNumbers [] = 0
-sumNumbers (x:xs) = x + sumNumbers xs
+
sumNumbers :: [Int] -> Int
+sumNumbers [] = 0
+sumNumbers (x:xs) = x + sumNumbers xs

Here’s how you compute the largest number in a list, this time using a helper function.

-
myMaximum :: [Int] -> Int
-myMaximum [] = 0       -- actually this should be some sort of error...
-myMaximum (x:xs) = go x xs
-  where go biggest [] = biggest
-        go biggest (x:xs) = go (max biggest x) xs
+
myMaximum :: [Int] -> Int
+myMaximum [] = 0       -- actually this should be some sort of error...
+myMaximum (x:xs) = go x xs
+  where go biggest [] = biggest
+        go biggest (x:xs) = go (max biggest x) xs

It’s often convenient to use nested patterns while consuming a list. Here’s an example that counts how many Nothing values occur in a list of Maybes:

-
countNothings :: [Maybe a] -> Int
-countNothings [] = 0
-countNothings (Nothing : xs) = 1 + countNothings xs
-countNothings (Just _  : xs) = countNothings xs
-
countNothings [Nothing,Just 1,Nothing]  ==>  2
+
countNothings :: [Maybe a] -> Int
+countNothings [] = 0
+countNothings (Nothing : xs) = 1 + countNothings xs
+countNothings (Just _  : xs) = countNothings xs
+
countNothings [Nothing,Just 1,Nothing]  ==>  2

3.8.4 Building and consuming a list

Now that we can build and consume lists, let’s do both of them at the same time. This function doubles all elements in a list.

-
doubleList :: [Int] -> [Int]
-doubleList [] = []
-doubleList (x:xs) = 2*x : doubleList xs
+
doubleList :: [Int] -> [Int]
+doubleList [] = []
+doubleList (x:xs) = 2*x : doubleList xs

It evaluates like this:

-
doubleList [1,2,3]
-=== doubleList (1:(2:(3:[])))
-==> 2*1 : doubleList (2:(3:[]))
-==> 2*1 : (2*2 : doubleList (3:[]))
-==> 2*1 : (2*2 : (2*3 : doubleList []))
-==> 2*1 : (2*2 : (2*3 : []))
-=== [2*1, 2*2, 2*3]
-==> [2,4,6]
+
doubleList [1,2,3]
+=== doubleList (1:(2:(3:[])))
+==> 2*1 : doubleList (2:(3:[]))
+==> 2*1 : (2*2 : doubleList (3:[]))
+==> 2*1 : (2*2 : (2*3 : doubleList []))
+==> 2*1 : (2*2 : (2*3 : []))
+=== [2*1, 2*2, 2*3]
+==> [2,4,6]

Once you know pattern matching for lists, it’s straightforward to define map and filter. Actually, let’s just look at the GHC standard library implementations. Here’s map:

-
map :: (a -> b) -> [a] -> [b]
-map _ []     = []
-map f (x:xs) = f x : map f xs
+
map :: (a -> b) -> [a] -> [b]
+map _ []     = []
+map f (x:xs) = f x : map f xs

and here’s filter:

-
filter :: (a -> Bool) -> [a] -> [a]
-filter _pred []    = []
-filter pred (x:xs)
-  | pred x         = x : filter pred xs
-  | otherwise      = filter pred xs
+
filter :: (a -> Bool) -> [a] -> [a]
+filter _pred []    = []
+filter pred (x:xs)
+  | pred x         = x : filter pred xs
+  | otherwise      = filter pred xs

(Note! naming the argument _pred is a way to tell the reader of the code that this argument is unused. It could have been just _ as well.)

3.9 Something fun: list comprehensions

Haskell has list comprehensions, a nice syntax for defining lists that combines the power of map and filter. You might be familiar with Python’s list comprehensions already. Haskell’s work pretty much the same way, but their syntax is a bit different.

Mapping:

-
[2*i | i<-[1,2,3]]
-  ==> [2,4,6]
-

Filtering:

-
[i | i <- [1..7], even i]
+
+

Filtering:

+

In general, these two forms are equivalent:

- +

List comprehensions can do even more. You can iterate over multiple lists:

- +

You can make local defitions:

- +

You can even do pattern matching in list comprehensions!

- - + +

3.10 Something fun: custom operators

In Haskell an operator is anything built from the characters !#$%&*+./<=>?@\^|-~. Operators can be defined just like functions (note the slightly different type annotation):

- - + +

3.11 Quiz

What’s the type of this function? both p q x = p x && q x

    @@ -1950,45 +1952,45 @@ Returns the second element of a list

    To get values out of tuples, you can use the functions fst and snd:

    - +

    You can also pattern match on tuples. This is often the most convenient way, and works for tuples of size >2. fst and snd work only on pairs.

    Tuples are very useful in combination with lists. Here are some examples using the zip, unzip and partition functions from the Data.List module.

    - + - +

    Here’s an example of pattern matching on tuples:

    - +

    Here’s an example of pattern matching on tuples and lists at the same time:

    - - + +

    4.2 Interlude: folding

    Consider the functions sumNumbers :: [Int] -> Int, myMaximum :: [Int] -> Int, and countNothings :: [Maybe a] -> Int again.

    ```haskell sumNumbers :: [Int] -> Int sumNumbers [] = 0 sumNumbers (x:xs) = x + sumNumbers xs

    myMaximum :: [Int] -> Int myMaximum [] = 0 myMaximum (x:xs) = go x xs where go biggest [] = biggest go biggest (x:xs) = go (max biggest x) xs

    - +

    They have one common characteristic. They take a list and produce a value that depends on the values of the elements in the given list. They “crunch” or fold a list of many values into a single value.

    Prelude has a function called foldr, which performs a right associative fold over a Foldable data type. We’ll learn more about Foldable soon. At this point, it suffices to think of lists, so we define

    - +

    What this definition says, is that for an empty list [] :: [a], foldr returns the default value y :: b. For any other list x : xs, foldr applies f to x and the result of foldr f y xs (i.e. folding over the rest of the list). It’s a simple definition by recursion.

    Consider the list [1,2,3]:

       (:)
    @@ -1999,11 +2001,11 @@ Returns the second element of a list
             /   \
            3    []

    The expression foldr (+) 0 [1,2,3] evaluates as follows:

    - +

    The result can be thought of as a tree:

       (+)
       /   \
    @@ -2013,12 +2015,12 @@ Returns the second element of a list
             /   \
            3     0

    One way to think about foldr f y xs is that it replaces the (:) operation with f and [] with y. In this case, f was (+) and y was 0. If you write out how sumNumbers [1,2,3] behaves, you’ll notice that it performs the same computation as foldr (+) 0 [1,2,3] does! More generally:

    - +

    Those more experienced with math may notice that we can prove this claim by induction: Firstly, sumNumbers [] ==> 0 and foldr (+) 0 [] ==> 0, so in the base case sumNumbers [] == foldr (+) 0 []. Next, we may assume as our induction hypothesis that sumNumbers xs == foldr (+) 0 xs for any list xs. Then, for the list x:xs, we have sumNumbers (x:xs) ==> x + sumNumbers xs. Hence, foldr (+) 0 (x:xs) ==> x + foldr (+) 0 xs ==> x + sumNumbers xs by induction hypothesis. Therefore, by induction, the equation holds.

    You don’t need to read, write, or understand induction proofs in this course, but perhaps it is reassuring to know that properties and equalities of functions in Haskell can be (in principle) analysed mathematically, because Haskell is such a nice language. (Equalities and properties can be analysed in any programming language, but for Haskell, this analysis is especially convenient because Haskell is pure.)

    Another folding example is the map function:

    - +

    To see why this works, consider what foldr helper [] [x1,x2,..,xn] does:

       (:)                                    helper
       /   \                                   /   \
    @@ -2046,27 +2048,27 @@ Returns the second element of a list
     

    The resulting list is then exactly what we would have gotten with map g xs. (This could have been also proved by induction as we did for sumNumbers.) The lesson to take away is that folding is a particular, yet quite general, way to apply some transformation recursively into some structure (e.g. a list).

    4.3 Type classes

    How can Haskell’s + work on both Ints and Doubles? Why can I compare all sorts of things with ==? We’ve briefly mentioned constrained types earlier. Let’s see what they really mean. Let’s look at the types of == and +.

    - +

    The type (Eq a) => a -> a -> Bool means: for all types a that belong to the class Eq, this is a function of type a -> a -> Bool. That is, if the type a is a member of the class Eq, you can give two values of type a to == and get a Bool result.

    - +

    Similarly, the type (Num a) => a -> a -> a means: for all types a that belong the the class Num, this is a function of type a -> a -> a. That is, you can give two values of the same type a to + and get out a third value of type a, as long as a is a member of Num.

    Num and Eq are type classes. A type class is a way to group together types that support similar operations.

    Note! a type class is a collection of types. It doesn’t have much to do with the classes of object oriented programming! In some situations, type classes can act like interfaces in object oriented programming. Unfortunately the functions in a type class are often called methods, adding to the confusion

    PS. remember how using type variables for polymorphism was called parametric polymorphism? The fancy word for what type classes achieve is ad-hoc polymorphism. The difference is that with parametric polymorphism the function (e.g. head) has the same implementation for all types, whereas with ad-hoc polymorphisms there are multiple implementations (consider == on numbers and strings).

    4.4 Type constraints

    When you’re working with a concrete type (not a type variable), you can just use type class functions (in this case, (==)):

    - +

    Of course, if the type in question isn’t a member of the right class, you get an error. For example:

    - +
    error:
         • No instance for (Num Bool) arising from a use of ‘+’
         • In the expression: b + True
           In an equation for ‘addTrue’: addTrue b = b + True

    However in a polymorphic function, you need to add type constraints. This doesn’t work:

    - +

    Luckily the error is nice:

    error:
         • No instance for (Eq a) arising from a use of ‘==’
    @@ -2077,205 +2079,205 @@ Returns the second element of a list
         • In the expression: x == g x
           In an equation for ‘f’: f g x = x == g x

    To signal that f only works on types that are members of the Eq class, we add a type constraint (Eq a) => to the type annotation.

    - +

    If you don’t have a type annotation, type inference can provide the constraints!

    - +

    You can also have multiple constraints:

    - +

    4.5 Standard type classes

    Here are some standard Haskell type classes you should know about.

    4.5.1 Eq

    We already saw the Eq class for equality comparisons. Here are the basic operations of the Eq class and some examples of their use. As you can see pretty much all the types we’ve seen so far, except for functions, are members of Eq.

    - - + +

    There are some other useful functions that use the Eq class, like nub from the module Data.List.

    - +

    4.5.2 Ord

    The Ord class is for ordering (less than, greater than). Again, here are the basic operations and some examples of their use. Note the new Ordering type. It has values LT for “less than”, EQ for “equal” and GT for “greater than”.

    - - + +

    When we can compare values, we can also sort lists of them. The function sort from Data.List works on all types that belong to the Ord class.

    - +

    As a last example, let’s sort a list of lists according to length. We’ll need two helper functions:

    - +

    Now the implementation of sortByLength is straightforward:

    - - + +

    4.5.3 Num, Integral, Fractional, Floating

    The Num class contains integer arithmetic:

    - +

    Num also shows up in the types of integer literals:

    - +

    Integral is the class of types that represent whole numbers, like Int and Integer. The most interesting functions are div and mod for integer division and remainder. All types that belong to Integral also belong to Num.

    - +

    Fractional is the class for types that have division. All types that belong to Fractional also belong to Num.

    - +

    Floating contains some additional operations that only make sense for floating point numbers. All types that belong to Floating also belong to Fractional (and to Num).

    - +

    4.5.4 Read and Show

    The Show and Read classes are for the functions show and read, that convert values to and from Strings.

    - - + +

    As you can see above, you often need to use a type annotation with read so that the compiler can choose the right implementation.

    4.5.5 Sidenote: Foldable

    One more thing! You might remember that it was mentioned earlier that the type of length isn’t [a] -> Int but something more general. Let’s have a look:

    - +

    This type looks a bit different than the ones we’ve seen before. The type variable t has an argument a. We’ll look at type classes like this in more detail in part 2, but here’s a crash course.

    What Foldable represents is types that you can fold over. The true type of foldr is:

    - +

    We’ve succesfully used the fact that lists are Foldable since we’ve managed to use length and foldr on lists. However, Maybe is also Foldable! The Foldable instance for Maybe just pretends that values of Maybe a are like lists of length 0 or 1:

    - +

    We’ll encounter another foldable type soon.

    4.6 More data structures

    Now that we are familiar with the standard type classes, we can look at one of their applications: the Map and Array data structures.

    4.6.1 Data.Map

    The Data.Map module defines the Map type. Maps are search trees for key-value pairs. One way to look at this is that a value of type Map k v is roughly the same as a value of type [(k,v)], a list of pairs. However, the operations on a map are more efficient than operations on a list.

    Since Data.Map contains some function with the same names as Prelude functions, the namespace needs to be imported qualified:

    - +

    Now we can refer to the map type as Map.Map, and to various map functions like Map.insert. Here are the most important functions for maps:

    - +

    The Ord constraint for the key type of the map is needed because maps are implemented as ordered binary search trees.

    Here’s an example of representing a bank as a Map String Int (map from account name to account balance), and withdrawing some money from an account:

    - +

    Here’s how you might use the withdraw function in GHCi. Note how the maps get printed as fromList invocations.

    - +

    Data.Map defines all sorts of useful higher-order functions for updating maps. We can rewrite the withdraw function using Data.Map.adjust:

    - +

    Note! There are separate Data.Map.Strict and Data.Map.Lazy implementations. When you import Data.Map you get Data.Map.Lazy. You can find the documentation for all the Data.Map functions in the docs for Data.Map.Lazy. We won’t go into their differences here, but mostly you shold use Data.Map.Strict in real code.

    4.6.2 Data.Array

    Another type that works kind of like a list but is more efficient for some operations is the array. Arrays are familiar from many other programming languages, but Haskell arrays are a bit different.

    Unlike the Data.Map module, the Data.Array can just be imported normally:

    - +

    Now we can look at the type of the array function that constructs an array.

    - +

    There are a couple of things to notice here. First of all, the Array type is parameterized by two types: the index type and the element type. Most other programming languages only parameterize arrays with the element type, but the index type is always int. In Haskell, we can have, for example, an Array Char Int: an array indexed by characters, or Array Bool String, an array indexed by booleans, or even Array (Int,Int) Int, a two-dimensional array of ints.

    Not all types can be index types. Only types that are similar to integers are suitable. That is the reason for the Ix i class constraint. The Ix class collects all the types that can be used as array indexes.

    Secondly, the array function takes an extra (i,i) parameter. These are the minimun and maximum indexes of the array. Unlike in other languages, where arrays always start at index 0 or 1, in Haskell you can define an array that starts from 7 and goes to 11. So here’s that array:

    - +

    Listing all the indices and elements in order can be a bit cumbersome, so there’s also the listArray constructor that just takes a list of elements in order:

    - - + +

    Arrays are used with two new operators:

    - +

    Here’s an example GHCi session:

    - +

    You might be wondering why the (//) operator does multiple updates at once. The reason is the main weakness of Haskell arrays: immutability. Since arrays can’t be changed in place, (//) must copy the whole array. This is why in Haskell it’s often preferable to use lists or maps to store data that needs to be updated. Arrays are still useful when you can construct then once and then do lots of lookups. We’ll get back to how Haskell data structures work in the next lecture.

    Note! In this course we’ll use only Array, a simple array type that’s specified in the Haskell standard. There are many other array types like the mutable IOArray and the somewhat obscure DiffArray. There are also type classes for arrays like IArray and MArray. In addition to arrays there is a wide family of Vector types that can be more practical than Array for real programs.

    4.6.3 Sidenote: folding over Maps & Arrays

    The Map and Array type are instances of Foldable just like lists are! This means you can use functions like length and foldr on them:

    - +

    4.7 Reading docs

    Haskell libraries tend to have pretty good docs. We’ve linked to docs via Hackage (https://hackage.haskell.org) previously, but it’s important to know how to find the docs by your self too. The tool for generating Haskell documentation is called Haddock so sometimes Haskell docs are referred to as haddocks.

    Hackage is the Haskell package repository (just like PyPI for Python, Maven Central for Java or NPM for Javascript). In addition to the actual packages, it hosts documentation for them. Most of the modules that we use on this course are in the package called base. You can browse the docs for the base package at http://hackage.haskell.org/package/base.

    @@ -2372,369 +2374,369 @@ It turns a list of pairs into a list of elements

    5.1 Algebraic Datatypes

    Haskell has a system called algebraic datatypes for defining new types. This sounds fancy, but is rather simple. Let’s dive in by looking at the standard library definitions of some familiar types:

    - +

    With this syntax you too can define types:

    - - + +

    5.1.1 Fields

    Types like Bool, Ordering and Color that just list a bunch of constants are called enumerations or enums in Haskell and other languages. Enums are useful, but you need other types as well. Here we define a type for reports containing an id number, a title, and a body:

    - +

    This is how you create a report:

    - +

    You can access the fields with pattern matching:

    - +

    5.1.2 Constructors

    The things on the right hand side of a data declaration are called constructors. True, False, Red and ConstructReport are all examples of constructors. A type can have multiple constructors, and a constructor can have zero or more fields.

    Here is a datatype for a standard playing card. It has five constructors, of which Joker has zero fields and the others have one field.

    - +

    Constructors with fields have function type and can be used wherever functions can:

    - +

    5.1.3 Sidenote: deriving

    By the way, there’s something missing from our Card type. Look at how it behaves compared to Ordering and Bool:

    - +

    The problem is that Haskell does not know how to print the types we defined. As the error says, they are not part of the Show class. The easy solution is to just add a deriving Show after the type definition:

    - - + +

    The deriving syntax is a way to automatically make your class a member of certain basic type classes, most notably Read, Show and Eq. We’ll talk more about what this means later.

    5.2 Sidenote: The case-of expression

    We’ve seen pattern matching in function arguments, but there’s also a way to pattern match in an expression. It looks like this:

    - +

    As an example let’s rewrite the describe example from the first lecture using case:

    - +describe 0 = "zero" +describe 1 = "one" +describe 2 = "an even prime" +describe n = "the number " ++ show n
+

A more interesting example is when the value were pattern matching on is not a function argument. For example:

- - + +

If we wanted to write search using normal pattern matching, we would need a helper function:

- +

5.3 Type parameters

We introduced type parameters and parametric polymorphism when introducing lists in Lecture 2. Since then, we’ve seen other parameterized types like Maybe and Either. Now we’ll learn how we can define our own parameterized types.

5.3.1 Defining parameterized types

The definition for Maybe is:

- +

What’s a? We define a parameterized type by mentioning a type variable (a in this case) on the left side of the = sign. We can then use the same type variable in fields for our constructors. This is analogous to polymorphic functions. Instead of defining separate functions

- +

and so on, we define one function head :: [a] -> a that works for all types a. Similarly, instead of defining multiple types

- +

we define one type Maybe a that works for all types a.

Here’s our first own parameterized type Described. The values of type Described a contain a value of type a and a String description.

- - + +

5.3.2 Syntactic note

In the above definitions, we’ve used a as a type variable. However any word that starts with a lower case letter is fine. We could have defined Maybe like this:

- +

The rules for Haskell identifiers are:

Note that a type and its constructor can have the same name.

- - + +

Beware of mixing up types and constructors. Luckily types and constructors can never occur in the same context, so you get a nice error:

- +

5.3.3 Sidenote: multiple type parameters

Types can have multiple type parameters. The syntax is similar to defining functions with many arguments. Here’s the definition of the standard Either type:

- +

5.4 Recursive types

So far, all of the types we’ve defined have been of constant size. We can represent one report or one colour, but how could we represent a collection of things? We could use lists of course, but could we define a list type ourselves?

Just like Haskell functions, Haskell data types can be recursive. This is no weirder than having an object in Java or Python that refers to another object of the same class. This is how you define a list of integers:

- +

We can use the functions defined above to work with lists of integers:

- +

Note that we can’t put values other than Ints inside our IntList:

- +

To be able to put any type of element in our list, let’s do the same thing with a type parameter. This is the same as the built in type [a], but with slightly clunkier syntax:

- +

Note how we need to pass the the type parameter a onwards in the recursion. We need to write Node a (List a) instead of Node a List. The Node constructor has two arguments. The first has type a, and the second has type List a. Here are the reimplementations of some standard list functions for our List type:

- - + +

Note that just like with normal Haskell lists, we can’t have elements of different types in the same list:

- +

5.4.1 Example: Growing a tree

Just like a list, we can also represent a binary tree:

- +

Our tree contains - nodes, which contain a value of type a and two child trees - empty trees

In case you’re not familiar with binary trees, they’re a data structure that’s often used as the basis for other data structures (Data.Map is based on trees!). Binary trees are often drawn as (upside-down) pictures, like this:

- +

The highest node in the tree is called the root (0 in this case), and the nodes with no children are called leaves (2, 3 and 4 in this case). We can define this tree using our Tree type like this:

- +

The height of a binary tree is length of the longest path from the root to a leaf. In Haskell terms, it’s how many nested levels of Node constructors you need to build the tree. The height of our example tree is 3. Here’s a function that computes the height of a tree:

- - + +

In case you’re familiar with binary search trees, here are the definitions of the lookup and insert opertions for a binary search tree. If you don’t know what I’m talking about, you don’t need to understand this.

- +

5.5 Record Syntax

If some fields need to be accessed often, it can be convenient to have helper functions for reading those fields. For instance, the type Person might have multiple fields:

- +

A list of persons might look like the following:

- +

Suppose that we need to find all engineers from Finland:

- +

Thus,

- +

Note that the types of the fields give little information on what is the intended content in those fields. We need to remember in all places in the code that town goes before state and not vice versa.

Haskell has a feature called record syntax that is helpful in these kind of cases. The datatype Person can be defined as a record:

- +

We can still define values of Person normally, but the Show instance prints the field names for us:

- -

However, we can also define values using record syntax. Note how the fields don’t need to be in any specific order now that they have names.

-
Prelude> MkPerson {name = "Jane Doe", town = "Houston", profession = "Engineer", state = "Texas", age = 21}
+
+

However, we can also define values using record syntax. Note how the fields don’t need to be in any specific order now that they have names.

+

Most importantly, We get accessor functions for the fields for free:

- +

We can now rewrite the query function using these accessor functions:

- +

You’ll probably agree that the code looks more pleasant now.

5.6 Algebraic Datatypes: Summary

  • Types are defined like this
- +
  • … or like this if we’re using type variables
- +
  • You can have one or more constructors
  • Each constructor can have zero or more fields
  • Constructors start with upper case, type variables with lower case
  • Values are handled with pattern matching:
- +
  • Constructors are just functions:
- +
  • You can also define datatypes using record syntax:
- +

this gives you accessor functions like field1 :: TypeName -> Field1Type for free.

5.7 Sidenote: other ways of defining types

In addition to the data keyword, there are two additional ways of defining types in Haskell.

The newtype keyword works like data, but you can only have a single constructor with a single field. It’s sometimes wise to use newtype for performance resons, but we’ll get back to those in part 2.

The type keyword introduces a type alias. Type aliases don’t affect type checking, they just offer a shorthand for writing types. For example the familiar String type is an alias for [Char]:

- +

This means that whenever the compiler reads String, it just immediately replaces it with [Char]. Type aliases seem useful, but they can easily make reading type errors harder.

5.8 How do algebraic datatypes work?

Remember how lists were represented in memory as linked lists? Let’s look in more detail at what algebraic datatypes look like in memory.

Haskell data forms directed graphs in memory. Every constructor is a node, every field is an edge. Names (of variables) are pointers into this graph. Different names can share parts of the structure. Here’s an example with lists. Note how the last two elements of x are shared with y and z.

- +

What happens when you make a new version of a datastructure is called path copying. Since Haskell data is immutable, the changed parts of the datastructure get copied, while the unchanged parts can be shared between the old and new versions.

Consider the definition of ++:

- +

We are making a copy of the first argument while we walk it. For every : constructor in the first input list, we are creating a new : constructor in the output list. The second argument can be shared. It is not used at all in the recursion. Visually:

- +

One more way to think about it is this: we want to change the tail pointer of the list element (3:). That means we need to make a new (3:). However the (2:) points to the (3:) so we need a new copy of the (2:) as well. Likewise for (1:).

The graphs that we get when working with lists are fairly simple. As a more involved example, here is what happens in memory when we run the binary tree insertion example from earlier in this lecture.

- - + +

Note how the old and the new tree share the subtree with 3 and 4 since it wasn’t changed, but the node 7 that was “changed” and all nodes above it get copied.

5.9 Quiz

What is the type of \x -> case x of (True, "Foo") -> show True ++ "Foo"?

@@ -2770,56 +2772,56 @@ Because Nothing is a constructor.

6 Lecture 6: Working Class Hero

We’ve seen class constraints like Eq a => in types. We know how to use existing classes with existing types. But how do we use existing classes with our own types? How can we define our own classes?

Here’s how to make your own type a member of the Eq class:

- +

A class instance is an instance block that contains definitions for the functions in that class. Here we define how == works on Color.

6.1 The syntax of classes and instances

A type class is defined using class syntax. The functions in the class are given types. Here’s a class Size that contains one function, size:

- +

Instances of a class are defined with instance syntax we’ve just seen. Here is how we make Int and [a] members of the Size class:

- +

Our class Size behaves just like existing type classes. We can use size anywhere where a function can be used, and Haskell can infer types with Size constraints for us:

- +

A class can contain multiple functions, and even constants. Here we define a new version of the Size class with more content.

- +

6.2 Sidenote: restrictions on instances

The Haskell 2010 allows only a very specific type of class instance. Let’s look at some instances that aren’t allowed. The examples use the Size class:

- +

We saw a Size [a] instance above. Why not define an instance just for lists of booleans?

- +
error:
     • Illegal instance declaration for ‘Size [Bool]’
         (All instance types must be of the form (T a1 ... an)
@@ -2837,153 +2839,153 @@ Because Nothing is a constructor.
 

The GHC Haskell implementation has many extensions to the type class system – we’ll get back to some of them in part 2 of this course.

6.3 Default implementations

Did you notice how in the previous example we gave sameSize the same definition in both instances? This is a very common occurrence, and it’s why Haskell classes can have default implementations. As a first example, here’s an Example type class for giving example values of types.

- +

Here’s how Example works. Note how the default implementation of examples got used in the Bool case but not in the Int case. Also note the need for explicit type signatures to tell GHCi which instance we’re interested in. Without them, we would get an “Ambiguous type variable” error.

- +

The standard type classes use lots of default implementations to make implementing the classes easy. Here is the standard definitions for Eq (formatted for readability).

- +

Note how both operations have a default implementation in terms of the other. This means we could define an Eq instance with no content at all, but the resulting functions would just recurse forever. In practice, we want to define at least one of == and /=.

When there are lots of default implementations, it can be hard to know which functions you need to implement yourself. For this reason class documentation usually mentions the minimal complete definition. For Eq, the docs say “Minimal complete definition: either == or /=.”

Let’s look at Ord next. Ord has 7 operations, all with default implementations in terms of each other. By the way, note the quirky way of defining multiple type signatures at once. It’s okay, it’s a feature of Haskell, this is how Ord is defined in the standard. (We’ll get back to what the (Eq a) => part means soon.)

- +

With this definition it’s really hard to know what the minimal complete definition is. Luckily the [docs tell us][ord-docs] “Minimal complete definition: either compare or <=.”

As a final word on default implementations, if there is never a need to override the default definition, the function can be moved out of the class for simplicity. Consider a class like Combine below:

- -

It’s hard to think of a case where combine3 would be given any other definition, so why not move it out of the class:

+ combine3 :: a -> a -> a -> a + combine3 x y z = combine x (combine y z)
+

It’s hard to think of a case where combine3 would be given any other definition, so why not move it out of the class:

+

6.3.1 Example: the Pair type

Here are the Eq and Ord instances for a simple pair type. Note how the definition uses the minimal complete definition rules by only defining == and <=.

- - + +

6.4 Useful stuff

6.4.1 Deriving

As we’ve seen many times already, deriving is a way to get automatically generated class instances. The Read and Show classes should pretty much always be derived to get the standard behaviour. The derived instance for Eq is typically what you want. It requires constructors and fields to match.

The derived Ord instance might not be what you want. It orders constructors left-to-right, and then compares fields inside constructors left-to-right. An example:

- - + +

6.4.2 Asking GHCi about classes

You can use the :info command in GHCi to get the contents and instances of a class. These days the info even includes the minimal complete definition (see the MINIMAL pragma). For example:

- +

6.5 Hierarchies

Both classes and instances can form hierarchies. This means that a class or instance depends on another class or instance.

Let’s start with a simple type class and instance

- +

Now we can write a function that checks a list:

- +

In order to turn this into a Check [a] instance, we need to add a constraint to the instance declaration. Our Check [a] is based on the Check a instance.

- +

This means that our Check [a] instance is only valid when there is a corresponding Check a instance. For example, if we try to invoke a Check [Bool] instance, we get an error about the missing Check Bool instance:

- +

Also, if we try to define Check [a] instance without the constraint, we get an error (with a pretty good suggestion!)

    • No instance for (Check a) arising from a use of ‘check’
       Possible fix:
         add (Check a) to the context of the instance declaration

If you think about it, this instance hierarchy allows us to circumventing the limitation that we can’t make a Check [Int] instance.

Respectively, a class can depend on another class. This is useful for instance when you want to use functions from another class in your default implementations:

- +

In cases like this we say SizeBoth is a subclass of Size. Note again the confusion with object oriented programming. Examples of subclasses in the standard library include:

- +

Another way to look at subclasses is that if you have a class Sub a => Main a, you must provide an instance Sub MyType in order to be able to declare instance Main MyType.

6.6 Quiz

What are the functions in the Eq class?

@@ -3026,15 +3028,15 @@ Because Nothing is a constructor.

Given the following definition of the class BitOperations

- +

which set of operations is not a minimal complete definition of BitOperations?

  1. @@ -3087,72 +3089,72 @@ If I define an instance for Num, I also get an instance for F

    7.1 Modeling with boxes

    Sometimes you don’t need a new type, but instead can just reuse a standard type. For example, repesenting car register plate numbers with String. However, if your code is full of Strings, it can be easy to accidentally mix up e.g. a car’s model and registration in a function like registerCar :: String -> String -> CarRegistry -> CarRegistry.

    For situations like this it’s common to create a new type that just contains a String (a “boxed” string):

    - +

    We can now give registerCar a slightly nicer type, String -> Plate -> CarRegistry -> CarRegistry. Additionally, we can restrict the operations that are possible on Plates to a subset of those that are possible on strings. For example, there is no need to combine the register plate numbers of two cars. Thus we don’t need to offer a function concatPlates :: Plate -> Plate -> Plate. We can also define a smart constructor for Plate that checks that the register number is in the correct format:

    - +

    Here’s another example: representing money. If we just store money as Ints, the compiler won’t protect us from mistakes like multiplying money with money. If instead we implement our own Money type that wraps Int, we get type safety. Additionally, we can encapsulate the fact that money is represented as an integer amount of cents.

    - - +

    7.2 Modeling with cases

    Haskell’s algebraic datatypes are really powerful at modeling things based on cases. It’s often useful to think of types as defining the set of possible cases, and functions handling those cases (often via pattern matching). Let’s look at two examples.

    Since it’s so easy to define custom types in Haskell, it’s quite convenient to use more descriptive types instead of booleans or strings. Consider a list of persons. In some other language if you wanted to sort the persons into ascending order by name you might use a call like sortPersons(persons, "name", true). In Haskell you can do this instead:

    - - + +

    Note how you can’t accidentally typo the field name (unlike with strings), and how you don’t need to remember whether true refers to ascending or descending order.

    Let’s move on to the next example. Many Haskell functions don’t work with empty lists (consider head []). If you’re writing code that needs to track whether lists are possibly empty or guaranteed to not be empty, you can use the NonEmpty type from the Data.List.NonEmpty module.

    Consider the definition of NonEmpty:

    - +

    Here the type represents a lack of cases. The type NonEmpty a will always consist of a value of type a, and some further as, collected in a list. Here are some example values of NonEmpty Int:

    - +

    Here are the functions that convert between normal lists and nonempty lists. Note how we can’t have a function [a] -> NonEmpty a, but must instead use Maybe to represent the possibility that the list was, indeed, empty. Note also how toList has only one equation, we can’t have a toList [] situation due to the type NonEmpty.

    - +

    In summary, if you write types that represent all possible cases for your values, and then write functions that handle those cases, your code will be simple and correct.

    7.3 Monoids

    7.3.1 What?

    @@ -3160,133 +3162,133 @@ If I define an instance for Num, I also get an instance for F

    The <> operation is meant to be associative that is, x <> (y <> z) == (x <> y) <> z. Familiar examples of associative operations include +, *, ++, max, etc.

    The mempty constant is meant to be a neutral element of the <> operation. This means that mempty <> x == x and x <> mempty == x. The neutral elements of +, * and ++ are 0, 1 and [], respectively.

    As we can guess at this point, there is a Monoid [a] instance that has (<>) = (++) and mempty = []:

    - +

    For a type like Int, it would be hard to pick a single instance Monoid Int since there are so many candidates for <>: at least +, *, max and min. That’s why the Haskell standard library defines the wrapper types Sum, Product, Max and Min. These types are from the Data.Monoid and Data.Semigroup modules.

    - +

    They work like this:

    - +

    This is another benefit of boxing things: being able to declare different type class instances!

    The reason we want both a neutral element and an associative binary operator is that those are the exact two things we need in order to reduce or fold multiple elements into one value. This is the job of:

    - +

    Sidenote: one way to define mconcat is foldr (<>) mempty. Do you remember foldr?

    Let’s look at why we need the properties of Monoid to implement mconcat. Firstly, we need mempty to handle empty lists:

    - +

    Secondly, we need associativity to be able to reduce a list [x,y,z] to a unique value. If <> were not associative, we would have two possible values for mconcat [x,y,z], namely (x<>y)<>z and x<>(y<>z).

    7.3.2 Why?

    What use is this Monoid class? Can’t we just write 1 + 2 instead of Sum 1 <> Sum 2? We can, yes, but some library functions work on all Monoid types. The most useful one is foldMap:

    - +

    That looks scary, but it’s very easy to use:

    - +

    Let’s break down that type. We know that an example of a Foldable t => t a type is [a], so we can rewrite the type as

    - +

    this function is easy to implement:

    - +

    Oh, by the way, thanks to the (Monoid a, Monoid b) => Monoid (a,b) instance we can even compute the maximum and product in one pass:

    - +

    Note, you don’t need to use monoids in your own code, but you’ll eventually bump into them when using Haskell libraries so it’s good to know what they are.

    7.3.3 How?

    Due to various historical and performance reasons, the definition of the Monoid class isn’t just

    - +

    although you can mostly pretend it is. The actual definition consists of two classes with a number of methods:

    - - -

    As you can see, all the operations except <> and mempty have default definitions, so a normal Monoid instance declaration looks just like this:

    - +

    As you can see, all the operations except <> and mempty have default definitions, so a normal Monoid instance declaration looks just like this:

    +

    7.4 Open and closed abstractions

    A question novice Haskell programmers often ask (or at least should ask!) is: when should I use type classes? This section offers one answer.

    Let’s look at a concrete example. A vehicle can be either a car or an airplane. We can model this with algebraic datatypes (as we’ve seen earlier in this chapter), but also with type classes. Here’s the datatype version:

    - +

    Here’s the class version. Note how each case gets its own datatype, which are collected together in a type class.

    - +

    What is the difference between these solutions? The data-based solution is closed, meaning the set of cases is fixed and we can handle all of them in one place. The class-based solution is open, meaning we can add new cases, even in other modules.

    An open abstraction is nice when we want extensibility. In the class-based solution, another module could define a bike:

    - +

    A closed abstraction is good when we want to know that we’ve handled all cases, consider for example the function canCollide which checks whether two vehicles can collide:

    - +

    This would be very hard to implement reliably in the class-based solution. Consider for example how collision checks between Bikes and Cars would get handled.

    7.5 Modeling with languages

    Sometimes it’s useful to implement a mini programming language for describing parts of your software. The fancy term for these is an Embedded Domain-Specific Language (EDSL). Haskell makes modeling and interpreting languages very easy. The expressions of the language are represented using (often recursive) algebraic data types. The language can be interpreted (that is, evaluated or run) by a recursive function.

    Here’s an example of a language for describing price computations for products in a web shop.

    - +

    The language is interpreted by the function applyDiscount that takes a customer name, a price, a discount, and returns a price.

    - +

    Here we apply a discount chain of -50%, -$30 with a minimum price of $35:

    - +

    Here we have different discounts for Ssarah and Yvonne:

    - +

    As you can see, even a simple Discount type can generate complex behaviours because it is self-referential (recursive). Using Discount we are able to represent the discount logic of our webshop as data instead of writing code. This data can be stored in a database, and we can even create multiple different interpreters for it. We could also visualize the discount chains in an administration user interface.

    7.6 Exercises

    @@ -3298,103 +3300,103 @@ If I define an instance for Num, I also get an instance for F

    8.1 A taste of IO

    This course has been centered about pure functional programming. We’ve done lots of arithmetic, reversed lists, worked with binary trees, but so far we haven’t been able to affect the world outside our GHCi.

    Things like reading input, writing to a file, or talking over the network are side effects. Side effects can’t be represented with pure functional code. A function like

    - +

    can’t be pure, because if it were, readInputFromUser "What is your name?" would always have to return the same result. However, representing side-effects and impurity in a pure language is possible. There are many ways of doing it, and the Haskell way is to use Monads.

    Monads are reputedly difficult to understand, but I think they’re easy to use in practice. Here’s a taste of the IO Monad, which you can use for all sorts of side effects in Haskell.

    Let’s start!

    - +
    another line
    - +

    What we’ve seen here is the IO action getLine. It has type IO String. This means that GHCi can execute the action to produce a value of type String. When we enter line <- getLine into GHCi, we mean:

    Execute the IO action getLine, and give the result the name line.

    After we’ve received line, it’s a pure String value and we can work with it normally.

    Some IO actions take parameters. For example putStrLn :: String -> IO () takes a String and returns an IO action that prints that string. The () type is a special type that only has one value, (). In this case IO () means that this IO always produces the same empty value (). You can run IO actions by just

    - +

    If you don’t need the return value of an IO action, you can run it in GHCi without the <-:

    - +

    You can build your own IO actions by combining other actions with do-notation. A do block lists IO actions that are executed in order.

    - - + +

    8.1.1 What about purity?

    It feels as if we can just do side effects where ever we want with these IO actions. However, it’s important to remember the distinction between defining an IO action and executing it.

    Let’s try to print while mapping over a list

    - - + +

    This didn’t print anything, because even though we defined our action, it wasn’t given to GHCi for execution. Because printAndIncrement returns an Int, it can’t return an action. Ok, let’s try another approach:

    - +

    That didn’t print anything either! Let’s see why:

    - +

    We generated a list of IO actions and computed the value of the list. Defining IO actions is pure, it’s running them that causes side effects. Since the type of our expression was Int, no IO actions could land in GHCi and be executed.

    If we instead return an IO action, it does get run:

    - +

    Here too, the code that produces the action putStrLn "string1" is pure, it’s only after the IO action is executed by GHCi that we see the printed string. And as you can see, the other IO action, putStrLn "string2", never got run.

    If this feels complicated, don’t worry. We’ll get back to this on part 2 of the course.

    8.1.2 What about Haskell programs?

    We know that GHCi can run IO actions. What about actual Haskell programs? The way Haskell programs work is that the IO action called main gets executed when the program is run. Recall our example program from Lecture 1.

    - +

    Here we see some pure code and a main IO action that prints two things (print is just putStrLn combined with show).

    We can place this code in a file called Gold.hs, compile it into an executable, and run it:

    - +

    8.2 Summary

    So far, we’ve learned about Haskell’s syntax and types, quite a bit of functional programming and about some language features like type classes.

    We’ve also seen some type-oriented programming, and even gotten a taste of I/O in Haskell.

    @@ -3420,6 +3422,10 @@ If I define an instance for Num, I also get an instance for F

    Open up the exercise file Set8.hs and follow the instructions there. Have fun!

    Remember to give feedback!

    8.5 Acknowledgements

    -

    Thanks to the whole Haskell Mooc team, especially - John Lång for help on the material - Antti Lappalainen for setting up the course and helping with arrangements

    +

    Thanks to the whole Haskell Mooc team, especially

    +
      +
    • John Lång for help on the material
    • +
    • Antti Laaksonen for setting up the course and helping with arrangements
    • +