Lecture 1 — 2017-08-28
Introduction
This lecture is written in literate Haskell; you can download the raw source.
To understand craftsmanship, we must ask not only “What has been made?” and “How has this been made?” but also “Who made this and why?” Craftsmanship is a relationship between the maker and the process of creation. It is not simply a set of skills one acquires, like the ability to read or drive a car. More than anything, craftsmanship is a matter of attitude: why we choose to devote time to such a demanding endeavor, why we choose to make a certain object of a certain appearance, and how we go about it.—Peter Korn, Woodworking Basics: Mastering the Essentials of Craftsmanship
import Prelude hiding ((.))
Let’s get started. Here’s a simple arithmetic function:
mean :: Int -> Int -> Int
mean x y = div (x + y) 2
We can make an ordinary function infix using backticks:
mean' :: Int -> Int -> Int
mean' x y = x + y `div` 2
Run ghci
to test your program. Here are some more interesting, recursive functions:
fact :: Int -> Int
fact 0 = 1
fact n = n * fact' (n - 1)
There are conditionals:
fact' :: Int -> Int
fact' n = if n <= 0
then 1
else n * fact' (n - 1)
Here’s the pipe form for conditionals within a top-level pattern match:
factPat :: Int -> Int
factPat :: Int -> Int
factPat n | n <= 0 = 1
factPat n = n * factPat (n - 1)
The following code is redundant and poorly typeset.
fact'' :: Int -> Int
fact'' 0 = 1
fact'' 1 = 1
fact'' 2 = 2
fact'' n = if n < 0 then 1
else n * fact'' (n - 1)
But don’t get the wrong idea: you can have more than one base case, if you like.
fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
Here’s an “accumulator passing” function, using a where
clause to nest a local definition. Can you see how fibAux
is like a loop?
fib' :: Int -> Int
fib' n = fibAux 1 1 n
where fibAux f2 f1 0 = f2
fibAux f2 f1 n = fibAux f1 (f1+f2) (n-1)
To see how important type annotations are to getting good error messages, try removing the type annotation on fibAux
and forgeting to apply fibAux
in the third case.
Here’s our first datatype, representing the days of the week.
data Day =
Sunday
| Monday
| Tuesday
| Wednesday
| Thursday
| Friday
| Saturday
deriving Show
Note the use of capital letters for the type itself (Day
) and for its constructors (Monday
, etc.). Try using these constructors in ghci
.
Here’s simple function to determine whether or not it’s party time; note how the wildcard pattern _
doesn’t bind any names and matches anything.
isWeekend :: Day -> Bool
isWeekend Saturday = True
isWeekend Sunday = True
isWeekend _ = False
Top-level definitions in Haskell automatically do pattern matching, but we can be explicit if we like:
isWeekend' :: Day -> Bool
isWeekend' d =
case d of
Saturday -> True
Sunday -> True
_ -> False
I think isWeekend'
’s definition is slightly less good than isWeekend
: why do an expression-level match when we could have matched at the top-level?
Let’s define isWeekday
in terms of a function we already have:
isWeekday :: Day -> Bool
isWeekday d = not (isWeekend d)
But we can also use the math-like composition operator:
isWeekday' :: Day -> Bool
isWeekday' = not . isWeekend
Before you go on, try to guess (.)
’s type. It’s an ordinary function!
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)
Finally, note that booleans aren’t builtins in Haskell. Somewhere in the Prelude (the standard definition), someone defined the booleans:
data Bool = False | True
And we could even define our own conditional:
myIf :: Bool -> a -> a -> a
myIf True t e = t
myIf False t e = e
factMine :: Int -> Int
factMine n = myIf (n <= 0) 1 (n * fact (n - 1))