The Roots of Functional Thinking
From lambda calculus and Lisp to ML, Haskell, Erlang, Scala, and the modern FP ecosystem.
Functional programming is older than most languages people use today. Its ideas were not invented in response to JavaScript or React or microservices — they were invented in the 1930s, before computers existed in any practical form. Understanding that lineage is the single best way to demystify FP. Most of the "weird" vocabulary (lambda, combinator, functor, monad) comes from a coherent intellectual tradition stretching back nearly a hundred years.
This chapter is the cliff-notes version of that history.
Lambda calculus: the math underneath
In 1936, the logician Alonzo Church introduced the lambda calculus: a tiny formal system in which the only things that exist are functions and applications of functions.
There are exactly three rules:
- A variable is an expression:
x - A function is an expression:
λx. body - An application is an expression:
(f x)
That's the entire language. No numbers, no booleans, no if, no
loops. Yet Church proved this system is Turing-complete:
anything any computer can compute can be expressed in lambda
calculus. Numbers, booleans, lists, recursion, and control flow can
all be encoded as functions.
The TypeScript equivalent of λx. x + 1 is the arrow function (x) => x + 1. The keyword lambda is almost everywhere in functional
languages — it's the name of the abstraction that wraps a body in a
parameter.
The key insight: a function is not just a tool inside a program. It is a value — the most fundamental value the language has. Everything in functional programming follows from taking that idea seriously.
Lisp: functions become a programming language (1958)
In 1958, John McCarthy designed Lisp at MIT. Lisp took the lambda calculus and turned it into a programmable language. Lists were the universal data structure. Code was data (you could pass functions around, build them at runtime, and even evaluate strings as programs). Recursion replaced loops.
Lisp introduced (or popularized) almost every idea that working programmers now take for granted: garbage collection, dynamic dispatch, the REPL, the function as a first-class value, the symbolic interpreter. Modern functional programming did not begin with Lisp, but it became usable with Lisp.
ML and Haskell: the type-theory branch (1973, 1990)
In 1973, Robin Milner designed ML to write proofs about programs. ML introduced the world to Hindley–Milner type inference — the idea that a compiler can figure out the types of your program automatically, even without annotations, and reject any program that doesn't type-check.
ML also introduced algebraic data types: a way to model "this is either an A or a B" so the compiler forces you to handle both cases. TypeScript's discriminated unions are a direct descendant.
In 1990, Haskell went further. It is a pure, lazy, statically
typed functional language. Pure means it has no side effects in
the language itself — every function returns a value and that's it.
Effects (I/O, randomness, mutation) are modeled as data and
sequenced by a special abstraction called a Monad. We will explore
this idea, gently, much later.
Haskell is where most of the vocabulary you'll hear in modern FP ("functor", "applicative", "monad", "kind", "typeclass") was crystallized. TypeScript cannot fully express these patterns — its type system lacks "higher-kinded types" — but the intuitions transfer perfectly.
Erlang: functional concurrency (1986)
While the academy refined types, Joe Armstrong and colleagues at Ericsson built Erlang to run telephone switches. The problem was not type safety but uptime: switches must run for years without crashing.
Erlang's answer was immutable values, lightweight processes, and message passing. Each phone call lived in its own process. The processes shared no memory. They communicated only by sending immutable messages. If a process crashed, a supervisor restarted it.
This combination — immutability plus isolated processes — is what later inspired Akka (Scala), the Actor model in Swift, Elixir, and parts of modern distributed systems design. Erlang showed that functional ideas were practical at industrial scale, not just elegant in a paper.
Scala, Clojure, F#: FP on top of mainstream platforms (2000s)
In the 2000s, FP arrived on the platforms developers actually shipped on:
- Scala brought ML-style types and Haskell-style abstractions to the JVM, while remaining interoperable with Java.
- Clojure brought Lisp and immutable data structures to the JVM.
- F# brought ML to .NET.
- OCaml continued in research and crept into industry (Jane Street, Facebook's Flow and Hack toolchain).
These languages proved you could write large business systems — banks, e-commerce, build tools — in a functional style without abandoning your existing infrastructure. They also normalized ideas that were once "academic": immutable collections, pattern matching, algebraic data types, persistent data structures.
JavaScript discovers functional programming
JavaScript had functions as first-class values from day one — it
inherited that from Scheme, a Lisp dialect. But for years, the
language was used mostly imperatively: for loops, mutation,
prototypes.
A few things changed that:
- jQuery and Underscore (2009-2010) popularized
.map,.filter,.reducefor collection processing. - ES5 (2009) added these methods to
Array.prototype. - Promises and ES6 (2015) made composing async operations feel more like building values than driving state machines.
- React (2013) treated the UI as a pure function of state — arguably the single biggest mainstreaming event for functional thinking in front-end work.
- Redux (2015) modeled state as immutable values transformed by pure reducers.
By 2018, "functional JavaScript" was no longer a niche style — it was the default idiom of the most popular UI library in the world. But pure JavaScript still lacked the type system to make functional design watertight. That's where TypeScript enters the story.
Why TypeScript matters here
JavaScript can imitate functional programming. TypeScript can enforce it. The same arrow function in both languages looks identical, but in TypeScript you can:
- Encode algebraic data types as discriminated unions, and have the compiler force exhaustive handling
- Express generic abstractions without losing precision
- Model
Option,Result,IO,Task, andStateas types - Detect entire classes of bug at compile time —
null, forgotten error cases, partial functions
This is what the rest of the course will build. The next chapter zooms in on the specific relationship between TypeScript's type system and functional programming, and previews the techniques we'll use throughout.
A short demo: the same idea, three eras
The classic "sum of squares of evens" problem, written in three historical voices:
All three produce 220. The first describes how to compute. The
second describes the recursive structure of the answer. The third
describes the answer itself, as a sequence of transformations. The
third is the style we will spend the rest of the course mastering.
What is the lambda calculus, and why does it matter for functional programming?
A category of bugs that occur in JavaScript closures
A library for working with mathematical functions in TypeScript
A 1936 formal system in which functions are the only primitive — Turing-complete and the foundation of functional programming
A type-inference algorithm used by Haskell and ML