λ

A language tour

The Purity of Haskell

A function has no side effects. A value never changes. The type system is a proof assistant. Correctness by construction.

scroll

01 — Pure Functions

The same input, always the same output

In Haskell, a function cannot read from a file, mutate a variable, or print to the screen — unless the type signature says so. This purity isn't a restriction: it's a guarantee. Pure functions are trivially testable, trivially composable, and trivially parallelisable.

"In Haskell, the purity of functions is enforced by the type system. A function that has effects must advertise those effects in its type — the type is a contract, and the compiler is the enforcement."

— Simon Peyton Jones, principal designer of GHC
pure.hs
-- Pure function: same inputs → always same output
fibonacci :: Int -> Int
fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n-1) + fibonacci (n-2)

-- Effectful function: IO in the type signature
greet :: String -> IO ()
greet name = putStrLn ("Hello, " ++ name ++ "!")

-- main lives in IO — it's the only place effects are allowed to escape
main :: IO ()
main = do
    putStrLn (show (map fibonacci [0..10]))
    greet "Haskell"

The type IO () is not a special keyword — it's an ordinary type. IO is a monad that sequences effects. The purity/effect boundary is tracked in the type system, not by convention.


02 — Type Inference

Types without the ceremony

Haskell has one of the most powerful type systems in any mainstream language — and you rarely need to write type annotations, because the compiler infers them. When you do write types, they serve as machine-checked documentation.

types.hs
-- Type signatures are documentation — not required but prized
square :: Num a => a -> a
square x = x * x

-- Works for Int, Integer, Double, Float — any Num instance
square 5    -- 25 :: Int
square 3.14 -- 9.8596 :: Double

-- Algebraic data types — model your domain precisely
data Shape
    = Circle    Double
    | Rectangle Double Double
    | Triangle  Double Double Double

area :: Shape -> Double
area (Circle r)       = pi * r * r
area (Rectangle w h) = w * h
area (Triangle b h _) = 0.5 * b * h

Pattern matching on ADTs is exhaustive — the compiler warns if you forgot a case. Adding a new constructor to Shape causes every function that pattern-matches on it to warn until you handle the new case.


03 — Lazy Evaluation

Infinite lists are fine

Haskell is lazy by default — expressions are evaluated only when their value is needed. This enables infinite data structures, separation of generation from consumption, and elegant definitions that would diverge in a strict language.

lazy.hs
-- Infinite list of all natural numbers — evaluates lazily
naturals :: [Integer]
naturals = [1..]

-- take forces only the first 10 elements
take 10 naturals  -- [1,2,3,4,5,6,7,8,9,10]

-- Infinite list of fibonacci numbers
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

-- The 50th fibonacci number — computed on demand
fibs !! 50  -- 12586269025

-- Sieve of Eratosthenes — elegantly lazy
primes :: [Integer]
primes = sieve [2..]
  where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]

fibs = 0 : 1 : zipWith (+) fibs (tail fibs) defines fibonacci as a self-referential lazy stream. It's one of the most cited examples of lazy evaluation's expressive power.


04 — Typeclasses

Polymorphism through constraints

Typeclasses are Haskell's mechanism for ad-hoc polymorphism — defining interfaces that any type can implement. They're the inspiration for Rust's traits, Swift's protocols, and Java's interfaces, but older and more powerful than all of them.

typeclasses.hs
-- Define a typeclass
class Describable a where
    describe :: a -> String

data Color = Red | Green | Blue

-- Implement for Color
instance Describable Color where
    describe Red   = "a warm red"
    describe Green = "a calm green"
    describe Blue  = "a cool blue"

-- Works for any Describable — the constraint is in the type
printDescription :: Describable a => a -> IO ()
printDescription x = putStrLn ("This is " ++ describe x)

-- Functor, Applicative, Monad are typeclasses too
-- fmap (+1) [1,2,3] == [2,3,4]  -- List is a Functor
-- fmap (+1) (Just 5) == Just 6  -- Maybe is a Functor

fmap has the type (a -> b) -> f a -> f b — it works for any Functor f. The same function maps over lists, Maybe, Either, trees, and any other container you define.


05 — Function Composition

Build programs from pieces

In Haskell, function composition is written with a dot: f . g means "apply g, then apply f." Programs are built by composing small functions into larger ones. Point-free style eliminates explicit arguments, expressing transformations as pipelines.

composition.hs
import Data.Char (toUpper, isAlpha)
import Data.List (nub, sort)

-- Compose functions with (.) — right to left
shout :: String -> String
shout = map toUpper . filter isAlpha

-- Point-free: no explicit argument needed
sortedUnique :: Ord a => [a] -> [a]
sortedUnique = sort . nub  -- deduplicate, then sort

-- ($) applies a function — useful for removing parentheses
main :: IO ()
main = do
    putStrLn $ shout "hello, world!"  -- HELLOWORLD
    print    $ sortedUnique [3,1,4,1,5,9,2,6]  -- [1,2,3,4,5,6,9]

Point-free style is not just aesthetic — it encourages thinking of functions as transformations to be composed, not procedures to be executed. The argument is just the data flowing through the pipeline.


06 — The Whole Picture

Why Haskell matters

🎓

The Research Language

More PL research has been done in or inspired by Haskell than any other language. Its influence runs through the industry.

💡

Ideas That Spread

Monads, type classes, GADTs, lazy evaluation — all pioneered in Haskell, all later adopted by mainstream languages.

🔒

Correctness

If it compiles, it often works. The type system encodes invariants that prevent entire categories of bug.

GHC

GHC is a world-class optimising compiler. Haskell programs can be competitive with C on the right workloads.

🏦

Finance

Standard Chartered, HSBC, and Jane Street use Haskell for financial modelling where correctness is non-negotiable.

🧩

The Challenge

Haskell is hard. That's part of the point — it expands how you think about types, effects, and program structure.