2.1 The basic types of Haskell
| Type | Values | Example literals |
|---|---|---|
Bool | truth values | True, False |
Int | fixed-size integers (at least 30 bits) | 42, -7 |
Integer | arbitrary-precision integers | 2^100 works exactly |
Float / Double | floating point numbers | 3.14, 2.0e3 |
Char | single characters | 'a', '9', '\n' |
String | = [Char], a list of characters | "hello" |
Every expression has a type, written with :: ("has type"):
True && False :: Bool
'x' :: Char
"functional" :: String
(3 < 5) :: Bool
2.2 Operators you must know
- Arithmetic:
+ - * /and for integersdiv,mod(div 7 2 = 3,mod 7 2 = 1— usually written infix with backquotes around the name), exponent^. - Comparison:
== /= < <= > >=— note /= is "not equal" (≠). - Boolean:
&&(and),||(or),not.
2.3 Function definitions — the three styles
1. Simple equation:
double :: Int -> Int
double n = 2 * n
2. Conditional expression (if–then–else). In Haskell if is an expression — it produces a value, so the else branch is compulsory:
maxOf2 :: Int -> Int -> Int
maxOf2 a b = if a >= b then a else b
3. Guards — the most readable style for multi-way decisions:
grade :: Int -> String
grade marks
| marks >= 90 = "A+"
| marks >= 75 = "A"
| marks >= 60 = "B"
| marks >= 40 = "C"
| otherwise = "Fail"
Guards are tried top to bottom; otherwise is just a constant equal to True, used as a catch-all.
2.4 Local definitions: where and let
-- where: definitions used by the whole right-hand side
heronArea :: Double -> Double -> Double -> Double
heronArea a b c = sqrt (s * (s-a) * (s-b) * (s-c))
where s = (a + b + c) / 2
-- let ... in: a local definition inside an expression
cylinderVol :: Double -> Double -> Double
cylinderVol r h = let base = pi * r * r in base * h
2.5 Multi-argument functions and currying
The type maxOf2 :: Int -> Int -> Int reads as Int -> (Int -> Int): maxOf2 takes one Int and returns a function that takes the second Int. This is called currying (after Haskell B. Curry — yes, the language is named after him too).
Practical payoff — partial application:
add :: Int -> Int -> Int
add x y = x + y
increment :: Int -> Int
increment = add 1 -- supply only the first argument!
ghci> increment 41
42
2.6 Designing and writing programs — the recipe
Haskell programs are designed type-first. For every function:
- Name it and write its type signature — what goes in, what comes out.
- Enumerate the cases of the input (zero/non-zero, empty/non-empty list, ...).
- Write one equation per case. Trust recursion for the "smaller" case.
- Test in GHCi with normal cases and edge cases.
Worked example — design power x n computing xⁿ for n ≥ 0:
-- Step 1: the type
power :: Double -> Int -> Double
-- Step 2: cases on n — n == 0 is the base case, n > 0 recursive
-- Step 3: one equation per case
power x 0 = 1.0
power x n = x * power x (n - 1)
-- Step 4: test
-- power 2 10 → 1024.0 ; power 5 0 → 1.0
Hand-evaluation (always practise this — it is a standard exam question):
power 2 3
= 2 * power 2 2
= 2 * (2 * power 2 1)
= 2 * (2 * (2 * power 2 0))
= 2 * (2 * (2 * 1.0))
= 8.0
2.7 Currying, precisely — how to read multi-arrow types
-> is right-associative and application is left-associative, so the two bracketings below say the same thing:
add :: Int -> Int -> Int means add :: Int -> (Int -> Int)
add 2 3 means (add 2) 3
Definition (currying). Representing a function of n arguments as a chain of n single-argument functions, each returning the next. The converse view — bundling the arguments into one tuple — is the uncurried form(Int, Int) -> Int. The standard converters arecurryanduncurry.
ghci> :t curry
curry :: ((a, b) -> c) -> a -> b -> c
ghci> uncurry add (2, 3)
5
Why exams love this: "Explain currying and partial application with an example" is a standard 5-mark question. Structure the answer as: definition → type bracketing → one partial-application example (like increment = add 1) → one practical payoff (map (add 10) xs needs no lambda).
2.8 More worked designs (each a past-style exam program)
Leap year with guards:
isLeap :: Int -> Bool
isLeap y
| y `mod` 400 == 0 = True
| y `mod` 100 == 0 = False
| y `mod` 4 == 0 = True
| otherwise = False
-- isLeap 2000 → True ; isLeap 1900 → False ; isLeap 2024 → True
Roots of a quadratic — where sharing a subcomputation, tuple result:
roots :: Double -> Double -> Double -> (Double, Double)
roots a b c = ((-b + d) / (2*a), (-b - d) / (2*a))
where d = sqrt (b*b - 4*a*c)
-- roots 1 (-5) 6 → (3.0, 2.0)
Greatest common divisor — recursion on numbers (Euclid):
hcf :: Int -> Int -> Int
hcf a 0 = a
hcf a b = hcf b (a `mod` b)
hcf 12 18 = hcf 18 12 = hcf 12 6 = hcf 6 0 = 6
2.9 let vs where — when to use which
| Feature | where | let ... in |
|---|---|---|
| Position | after the whole right-hand side | inside any expression |
| Scope | all guards/equations of that clause | only the in expression |
| Is it an expression? | no (syntax attached to a definition) | yes — usable anywhere an expression fits |
| Typical use | shared helper across several guards | small local value mid-computation |
Both may define several names and even local functions; indentation determines where the block ends (the layout rule: definitions in a block must start in the same column).
2.10 Common beginner errors the examiner probes
| Mistake | Why it fails | Fix |
|---|---|---|
square -2 | parsed as square - 2 (subtraction!) | square (-2) |
if x > 0 then 1 | if is an expression — else compulsory | add else 0 |
f x+1 hoping for f (x+1) | application binds tighter than + | f (x+1) |
3 + 4.5 with 3 :: Int | Int and Double never mix silently | fromIntegral n + 4.5 |
Guard with no otherwise falling through | non-exhaustive guards crash at runtime | end with otherwise |
Exam pointers
- Memorise the basic-types table (§2.1) — "list the basic types of Haskell with examples" is a direct 3-mark question.
- For any "write a function" question: signature first, cases second, equations third, then a two-line test — this mirrors the design recipe of §2.6 and reads like a model answer.
- Hand-evaluations (like
power 2 3orhcf 12 18) must shrink visibly each step; the marker checks the base case is reached.
Self-check
- Rewrite
gradefrom §2.3 usingif-then-elseonly. Which version is clearer and why? - What is the type of
add 1ifadd :: Int -> Int -> Int? - Why must
sinheronAreabe in awhereblock rather than computed three times inline? (Two reasons: readability and sharing.) - Evaluate
hcf 48 18by hand. - State the difference between
divand/, including the type classes they belong to.