Day 1 of 5
⏱ ~60 minutes
Haskell in 5 Days — Day 1

Pure Functions & Types

Haskell is a purely functional language with a strong static type system. Functions have no side effects, data is immutable by default, and the type checker prevents entire categories of bugs at compile time. Today you learn the fundamentals.

Purity and Immutability

A pure function always returns the same output for the same input and has no side effects. In Haskell, this is enforced by the type system — a function that does I/O must have the IO type in its signature, making side effects explicit and visible. Immutability means variables never change; instead, you create new values. This makes reasoning about code far easier and enables safe parallelism.

The Haskell Type System

Haskell's type system is Hindley-Milner — it infers types without annotations in most cases. Types: Int, Integer (arbitrary precision), Double, Bool, Char, String ([Char]), Maybe a (optional value), Either e a (error or value), and [a] (list). Type classes define behavior: Eq (equality), Ord (ordering), Show (to string), Num (arithmetic), Functor, Monad. Types are checked at compile time; runtime type errors are essentially impossible.

Function Definition and Pattern Matching

Functions are defined with equations. Pattern matching deconstructs values: match on constructors, literals, or wildcards (_). Guards add conditions. Where clauses define local bindings. Let expressions define local bindings inside an expression. Function application is left-associative and highest precedence: 'f x y' means '(f x) y'. The ($) operator applies a function to an argument: 'f $ g x' means 'f (g x)'.

haskell
-- Type signatures are optional but idiomatic
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

-- Pattern matching on lists
listSum :: [Int] -> Int
listSum []     = 0
listSum (x:xs) = x + listSum xs

-- Guards
bmiCategory :: Double -> String
bmiCategory bmi
  | bmi < 18.5 = "Underweight"
  | bmi < 25.0 = "Normal"
  | bmi < 30.0 = "Overweight"
  | otherwise  = "Obese"

-- Where clause
circleArea :: Double -> Double
circleArea r = pi * r2
  where r2 = r * r

-- Let expression
cylinderVolume :: Double -> Double -> Double
cylinderVolume r h =
  let area = pi * r * r
  in  area * h
💡
Write type signatures for all top-level functions even though Haskell can infer them. They serve as documentation, and the compiler will tell you if your implementation doesn't match your stated intent.
📝 Day 1 Exercise
Write Pure Functions
  1. Install GHC via GHCup: curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
  2. Open GHCi (the REPL): ghci
  3. Define factorial and test: factorial 10 should return 3628800
  4. Write a function isPrime :: Int -> Bool using guards and recursion
  5. Write a function digits :: Int -> [Int] that returns the digits of a number as a list

Day 1 Summary

  • Pure functions have no side effects and always return the same output for the same input
  • Haskell infers types via Hindley-Milner — annotations are optional but idiomatic
  • Pattern matching deconstructs data; guards add boolean conditions
  • Type classes (Eq, Ord, Show, Num) define behaviors implemented by many types
  • The IO type makes side effects explicit and visible in function signatures
Challenge

Write a complete Caesar cipher in Haskell: encode :: Int -> String -> String and decode :: Int -> String -> String. Handle both upper and lower case letters; leave non-letters unchanged.

Finished this lesson?