λ haskeller
I · II · III · IV · V · VI
a small notebook on thinking in lambda

This is a small notebook about thinking in .

— scroll, slowly

I prelude :: Reader -> Page

prelude, for the patient reader

“a program is a proof in disguise” — pencilled in a margin, ca. 1989

Welcome. This page is not a product launch. It is a notebook, kept by a person who has spent an unreasonable amount of time arranging values into types and types into proofs. If you are looking for a download link, a pricing tier, or a friendly chatbot to onboard you, you have arrived at the wrong manuscript — though you are very welcome to stay and read.

The intended reader is anyone willing to slow down. You may be a working Haskell programmer, a curious onlooker, or simply someone who likes the way >>= looks on the page. The folios that follow are short essays — six in total — each ending with a one-line equation that I hope earns its -- QED.

The intuition first. Functional programming is, for me, less a technology than a habit of thought: the habit of asking what a thing is before asking what it should do. The folios that follow are arranged like a small proof tree — premises above, conclusion below — in the hope that, if you read them in order, the last folio will feel like the obvious next line.

There are six folios. The first explains who this is for; the second sketches what it is to think in types; the third treats equational reasoning as an everyday tool; the fourth meditates on three quiet virtues — laziness, purity, and totality; the fifth is about composition as a way of life; and the sixth is the only place where anything actually happens.

prelude = readWith patience . turnPage -- QED

II data Haskeller = ...

thinking, in types

“a type is a proposition; a program, its proof.” — Curry & Howard, ambient

A Haskeller is, before anything else, a person with a peculiar bias: given any value in the wild, they would like first to know its type. Not its method signature, not its class hierarchy — its type, in the strictest sense, the small declaration that says “this is the shape of the thing, and these are the only honest moves you can make with it.”

The intuition. A type is not a label; it is a constraint on dishonesty. The function id :: a -> a can do nothing but return its argument — the type alone forbids every other behaviour. The Haskeller learns to read this as a relief, not a restriction. A good type signature shrinks the space of possible programs to a small, inhabited room.

To think in types is to begin every problem by writing down the shape of the answer before composing its body. parse :: String -> Maybe AST is already half a program; the other half is the obligation that the body satisfy that promise. The discipline is austere — one writes signatures into a notebook before opening an editor — and it is also generous: the compiler becomes a quiet correspondent, returning early drafts with politely pencilled corrections.

Over years this rearranges the shape of one’s thinking. One starts to suspect that a recipe, a contract, a piece of bureaucracy, an essay — all of them have types, in a sense, and that confusion is, often enough, the result of a value being silently coerced into the wrong one.

id :: a -> a -- QED

III instance Functor Mind

equational, reasoning

“equals may be substituted for equals.” — Bird & Wadler, underlined

The most quietly radical thing Haskell offers is not laziness, nor type classes, nor monads. It is the freedom to read a program the way one reads a proof: by substituting equals for equals. If map f . map g is, by law, equal to map (f . g), then anywhere in any program one may write the second instead of the first, and the meaning is exactly the same.

The intuition. Laws are not constraints; laws are permissions. The functor laws give you permission to fuse two traversals into one without holding your breath. The monoid laws give you permission to re-associate a long sum without re-running the calculation. A program written with these permissions in mind is a program one can refactor by hand, on paper, in pencil.

fmap f . fmap g fmap (f . g) -- functor composition

In practice this changes the texture of one’s working day. Bugs become small theorems with missing premises. Refactors become rewriting steps in a derivation. One stops asking “does this still work?” and starts asking “is this still equal to what I had before?” The questions look similar but the second is answerable by inspection.

The habit leaks. One begins to spot, in ordinary speech, statements that almost commute, almost associate, almost factor. One learns to notice when a sentence has been re-arranged in a way that secretly changes its meaning. This is, in its own way, a form of attention.

fmap f . fmap g fmap (f . g) -- QED

IV do { laziness ; purity ; ... }

three quiet, virtues

“evaluate only what you must, when you must.” — a maxim, restated

Laziness. A Haskell expression is a promise, not a value. The promise is kept only when, and only as much as, someone needs its result. This sounds like a performance trick and is, in practice, something far stranger: it lets one define infinite data structures and only ever pay for the prefix one inspects. The list of all primes is a perfectly ordinary value here.

The intuition. Laziness is what makes take 10 . filter prime $ [2..] a sensible sentence. The producer and the consumer can be written, by different people, in different files, and meet exactly in the middle — only the first ten primes are ever computed. This is composition’s secret co-conspirator.

Purity. A pure function takes its inputs and returns its output, and that is the entire story. It does not log, mutate, network, or sigh. This is not a moral position; it is a practical one. A pure function can be tested with three examples and trusted in three thousand contexts. It is, in the textbook sense, referentially transparent — one may replace it with its result wherever it appears, and the program means the same thing.

Totality. A total function returns a sensible value for every input. head [] is the small, ancient sin against this virtue, and the cure is to teach a function to say so in its type: safeHead :: [a] -> Maybe a. Totality is honesty about partiality — the polite refusal to lie about what one’s function does in the cases its author hadn’t quite imagined.

Held together, these three virtues produce code that is easy to read, easy to refactor, and surprisingly hard to break. They are the small house-rules of a long-running practice.

laziness purity totality trust -- QED

V (>>=) :: m a -> (a -> m b) -> m b

composition, as a way of life

“the dot is the thing.” — pencilled, twice

The smallest, most innocuous-looking operator in Haskell is the period: (.). It is the act of plugging the output of one function into the input of another, and it has the type (b -> c) -> (a -> b) -> (a -> c). Read it slowly. It says: if you can step from a to b, and from b to c, you can step from a to c. This is the entire game.

The intuition. A program is mostly a pipeline of small, well-named steps. Each step is a function with a tight type. The operator . connects two pipes; the operator >>= connects two pipes when one of them carries a context (a possibility of failure, a side-effect, a list of branches). One reads the program top-to-bottom and the data flows left-to-right.

f >=> (g >=> h) (f >=> g) >=> h -- monad associativity

The bind operator >>= is the same idea, but it threads a context. Reading parseUser >>= validateAge >>= save, one reads three small steps in a row, each of which may fail, and the failure is plumbed automatically. The story is told once, in a single line.

The daily consequence is a peculiarly even style: small named functions, composed in pipelines, each pipeline small enough to read aloud. There is no for-loop ceremony, no early return, no nested conditional pyramid — only steps, and the dot between them.

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

VI main :: IO ()

main, where the world begins

“the program is a value; main is its only sin.” — an old joke

Every Haskell program ends, eventually, at main. main is the one place where the elegant little value-language brushes against the untidy world — where files are read, sockets opened, screens drawn. Its type is short and honest: IO (). It promises to do some effects and return nothing in particular.

The intuition. The point of IO () is not that effects are bad. The point is that effects are marked. A function with type String -> String cannot, by construction, send an email; a function with type String -> IO String just might. The type is a small public declaration: this thing touches the world.

There is, then, no closing call to action here. No newsletter, no demo booking, no cleverly placed footer button. If anything in this notebook has been useful, it has already done its work. Close the tab; open Bird & Wadler; write a small function with a type signature you are proud of. The folios will still be here when you come back.

Thank you for reading slowly. — the haskeller

main = putStrLn "λ" -- QED