Haskell
First appeared 1990 · Simon Peyton Jones
Purely functional and lazily evaluated — where the type system does most of the arguing.
Overview
Haskell is a purely functional, statically typed, lazily evaluated language with one of the most advanced type systems in practical use. Purity means functions cannot perform side effects unless their type says so, which makes the type signature an unusually honest description of what a function does — a function returning Int cannot secretly write to a file or mutate global state. Laziness means expressions are not evaluated until their result is needed, enabling infinite data structures and a compositional style that would be prohibitively wasteful in a strict language. Type classes provide principled ad-hoc polymorphism, and higher-kinded types allow abstractions such as Functor, Monad and Traversable that most languages cannot express at all. Haskell's practical reputation is mixed: it is genuinely excellent for compilers, financial systems and anywhere correctness is paramount, and genuinely demanding to learn, hire for and reason about in terms of performance.
Key facts
The reference details, without the paragraph.
- First appeared
- 1990
- Designed by
- A committee of functional programming researchers; GHC largely shaped by Simon Peyton Jones
- Typing
- Static, strong, inferred, with type classes and higher-kinded types
- Evaluation
- Lazy by default — expressions are not evaluated until their value is demanded
- Purity
- Functions have no side effects unless their type says so
- Execution
- Compiled to native code by GHC
- Package manager
- Cabal or Stack, backed by Hackage and Stackage
- File extensions
- .hs, .lhs
- Named after
- Haskell Curry, the logician
- Licence
- BSD 3-clause
History
How the language got here — the decisions that still shape how you write it.
Haskell was born from a committee, which is unusual for a language that succeeded. By the late 1980s there were more than a dozen competing lazy functional languages, each with a small research community, and at the 1987 Functional Programming Languages and Computer Architecture conference the researchers agreed to consolidate around a single open standard. The result, named after the logician Haskell Curry, first appeared in 1990. Its most consequential contribution came in 1991 when Philip Wadler adapted monads from category theory to structure input and output — solving the long-standing problem of how a pure language performs side effects without abandoning purity, a pattern that has since spread to Rust, Scala, JavaScript and beyond. The Glasgow Haskell Compiler, largely shaped by Simon Peyton Jones, became the de facto implementation and a remarkable research vehicle in its own right. Haskell was deliberately positioned to avoid success at all costs, a joke about preserving freedom to change the language rather than being frozen by industrial adoption. It nevertheless found real production use at financial firms, at Meta for spam filtering, and in the compiler and blockchain worlds, while remaining most influential as the place where ideas are proven before appearing elsewhere.
- 1987
A committee agrees to consolidate
With more than a dozen competing lazy functional languages fragmenting the research community, the FPCA conference agrees to design a single open standard.
- 1990
Haskell 1.0
The first report is published, establishing lazy evaluation, purity and type classes as the language's defining commitments.
- 1991
Monads solve input and output
Philip Wadler adapts monads from category theory to sequence effects in a pure language — the idea that made Haskell practical, and which has since spread to Rust, Scala and JavaScript.
- 2003
The Haskell 98 report
A stable, conservative standard is published, giving the language a fixed target while GHC continues to explore beyond it.
- 2006–2010
Software transactional memory and parallelism
GHC gains STM and lightweight threads, making Haskell unusually good at concurrency — purity means most data is safe to share by construction.
- 2015
Industrial use becomes visible
Meta's Sigma spam-detection engine, Standard Chartered's trading systems and several compiler projects demonstrate Haskell running real production workloads.
- 2020–2025
Tooling finally catches up
The Haskell Language Server, GHCup and improved error messages remove much of the friction that historically made the language harder to approach than it needed to be.
What it is good at
The reasons teams pick it, stated concretely.
The type signature tells the truth
A function typed `Int -> Int` cannot read a file, mutate state or throw. Effects must appear in the type, which makes signatures an unusually reliable summary of what code does — and refactoring correspondingly safer.
If it compiles, it very often works
This is a cliché but has real substance: exhaustive pattern matching, no null, no implicit conversions and effects tracked in types eliminate entire categories of bug before the program runs.
Composition all the way down
Higher-order functions, currying and laziness make building programs from small pieces genuinely natural. Function composition is the default structuring tool, not a stylistic choice.
Excellent concurrency and parallelism
Green threads are extremely cheap, software transactional memory makes shared state composable without lock ordering, and purity means most values are trivially safe to share across threads.
It changes how you write everything else
Immutability, algebraic data types, and separating pure logic from effects are ideas most Haskell learners carry into their other languages. Its influence far exceeds its production footprint.
Trade-offs
Every language costs you something. Knowing what, before you commit, is the whole point.
Laziness makes performance hard to predict
Unevaluated thunks accumulate silently and can exhaust memory in a program that looks like it should stream. Diagnosing a space leak requires understanding evaluation order, strictness annotations and profiling — this is Haskell's most persistent practical problem.
A long conceptual ramp
Monads, functors, applicatives, monad transformers and type classes must be genuinely understood, not memorised. Most people find the first month disorienting in a way that learning another imperative language never is.
A small hiring pool
Haskell developers are scarce and teams generally have to train people in. That works, but it is a real organisational cost and a common reason companies choose something else.
Library depth is uneven
Hackage covers a great deal, but quality and maintenance vary widely, and several problems have three competing libraries with no clear winner. Stackage curation helps considerably.
Records and strings are historically awkward
The original record system has poor field namespacing, and there are four string types — String, Text, lazy Text and ByteString — that must be converted between. Both have improved but remain friction.
Code examples
Not syntax tours — the idioms that make code read like the language rather than a translation of another one.
-- An algebraic data type: exactly these cases, no others.
data Shape
= Circle Double
| Rectangle Double Double
| Triangle Double Double
deriving (Show, Eq)
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
area (Triangle b h) = b * h / 2
-- Add a fourth constructor and -Wincomplete-patterns warns here.
-- Maybe replaces null; the type forces the caller to handle absence.
safeDivide :: Double -> Double -> Maybe Double
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)
describe :: Maybe Double -> String
describe Nothing = "undefined"
describe (Just v) = "result: " ++ show v-- IO in the return type is the compiler recording that this touches
-- the outside world. A pure function cannot call it and hide the fact.
loadConfig :: FilePath -> IO (Either String Config)
loadConfig path = do
exists <- doesFileExist path
if not exists
then pure (Left ("missing: " ++ path))
else do
contents <- readFile path
pure (parseConfig contents) -- parseConfig is pure
-- Pure: same input, same output, no effects, trivially testable.
parseConfig :: String -> Either String Config
parseConfig = ...
main :: IO ()
main = do
result <- loadConfig "app.conf"
case result of
Left err -> hPutStrLn stderr err >> exitFailure
Right cfg -> runApp cfgclass Describable a where
describe :: a -> String
describe _ = "unknown" -- a default implementation
instance Describable Shape where
describe (Circle r) = "circle of radius " ++ show r
describe other = show other
-- Constrained polymorphism: works for any type with an instance.
summarise :: Describable a => [a] -> String
summarise = intercalate "; " . map describe
-- Functor, Applicative and Monad abstract over container-like types,
-- so the same combinators work across Maybe, Either, lists and IO.
fmap (+1) (Just 2) -- Just 3
(+) <$> Just 2 <*> Just 3 -- Just 5
traverse safeParse ["1", "2", "x"] -- Nothing — one failure fails all-- Laziness makes infinite structures ordinary.
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
take 10 fibs -- [0,1,1,2,3,5,8,13,21,34]
-- And it makes this quietly disastrous: foldl builds a chain of
-- unevaluated thunks the size of the input before computing anything.
sumBad :: [Int] -> Int
sumBad = foldl (+) 0 -- can exhaust memory on a large list
-- foldl' forces each step, so memory stays constant.
import Data.List (foldl')
sumGood :: [Int] -> Int
sumGood = foldl' (+) 0
-- Strictness annotations in data types prevent the same problem.
data Stats = Stats { count :: !Int, total :: !Double }Common pitfalls
The mistakes that cost everyone an afternoon at least once.
Space leaks from lazy accumulation
`foldl`, lazy state and non-strict record fields build chains of thunks that consume memory proportional to the input. Use `foldl'`, strict fields with `!`, and profile with `-hT` when memory grows unexpectedly.
Using String for real work
`String` is a linked list of Char — enormously wasteful. Use `Text` for human-readable text and `ByteString` for binary, and enable OverloadedStrings so literals work with both.
Reaching for monad transformers too early
A deep `ReaderT (StateT (ExceptT IO))` stack is hard to read and hard to change. Start with `ReaderT env IO` and add layers only when a concrete need appears.
Partial functions from the Prelude
`head`, `tail`, `fromJust` and `read` all throw on unexpected input, discarding the safety the type system provides. Use pattern matching, `Data.Maybe.listToMaybe`, or the `safe` package.
Not compiling with -Wall
Incomplete pattern matches and unused bindings are warnings, not errors, and off by default. `-Wall -Werror` in CI catches a meaningful class of bug.
Fighting the record system
Field names share a module namespace, so two types cannot both have a `name` field without extensions. Enable `DuplicateRecordFields` and `OverloadedRecordDot`, or use a lens library.
In production
Where it is running at scale, and what it is doing there.
Meta
Sigma, its rule engine for spam and abuse detection, processes millions of requests per second.
Standard Chartered
One of the largest commercial Haskell codebases, used across its markets business.
Input Output
The Cardano blockchain and its formally specified ledger are written in Haskell.
Target
Supply chain optimisation systems.
Learning path
A realistic order to learn things in, with something to build at each step.
- 1
Weeks 1–3
Thinking in types and functions
Types and type signatures, pattern matching, algebraic data types, recursion, higher-order functions, currying and Maybe. Do not attempt monads yet — build intuition for pure functions first.
Build this: Write a program that parses and analyses a text file, with all logic in pure functions.
- 2
Weeks 4–6
Type classes and the abstractions
Functor, Applicative, Monad, Foldable, Traversable — in that order, because each generalises the previous. Learn what they abstract over rather than memorising laws.
Build this: Implement Functor and Monad instances for a small type of your own and see the laws motivate themselves.
- 3
Weeks 7–10
IO, effects and laziness
do notation, IO, Either for errors, and the practical consequences of laziness — thunks, strictness annotations, foldl versus foldl'. Learn to read a heap profile.
Build this: Build a CLI tool with real file and network IO, then profile it and remove a space leak.
- 4
Months 3–5
Building real programs
Cabal or Stack, Text and ByteString, Aeson for JSON, Servant for APIs, and monad transformers or an effect system for structuring larger applications.
Build this: Build and deploy a small web API with a database behind it.
- 5
Ongoing
The deep end
GADTs, type families, DataKinds, STM and concurrency, GHC extensions and performance tuning. Read GHC's Core output when performance matters — it shows what the optimiser actually did.
Build this: Take a slow program, profile it, and make it fast by controlling strictness rather than rewriting the algorithm.
Ecosystem and tooling
The tools you will end up installing whichever project you join.
| Tool | Category | What it does |
|---|---|---|
| GHC | Compiler | The compiler — effectively the language implementation, and a research vehicle in its own right |
| GHCup | Tooling | Installs and manages GHC, Cabal, Stack and the language server; the modern starting point |
| Cabal / Stack | Build | Build and dependency management; Stack adds curated, reproducible snapshots |
| Hackage / Stackage | Packaging | The package archive, and a curated set of versions known to build together |
| Haskell Language Server | Tooling | IDE support — completion, types on hover, refactoring; a large improvement in approachability |
| Aeson | Data | The standard JSON library, with generic derivation from data types |
| Servant | Web | Web APIs described as types, generating server, client and documentation from one definition |
| QuickCheck / Hspec | Testing | Property-based testing — QuickCheck originated the idea that Hypothesis and proptest later copied |
Haskell libraries
Library coverage for Haskell is on the way.
The guide above is complete. In the meantime, the catalogues for Python, Java, JavaScript, C and C++ are fully written.
Browse all librariesFrequently asked
Do I need to understand category theory?
No. Monads and functors are named after category theory concepts, but you can use them competently by understanding what they do — sequencing computations, mapping over a structure — without any of the mathematics. The theory is interesting afterwards, not a prerequisite.
Is Haskell practical for production?
Yes, with caveats. Meta, Standard Chartered, Input Output and others run substantial Haskell systems, and it excels at compilers, financial modelling and anywhere correctness dominates. The costs are real: hiring is hard, performance requires understanding laziness, and library quality is uneven. It is a deliberate choice, not a default one.
What is laziness actually good for?
It lets you define infinite structures, separate generation from consumption, and write code that only computes what is needed — `take 10 (filter p [1..])` works without a manual loop bound. The cost is unpredictable memory behaviour, which is the trade most experienced Haskellers would say is the language's most debatable decision.
Should I learn Haskell if I will never use it at work?
It is one of the highest-return languages to learn for its influence on how you write everything else. Immutability, algebraic data types, making illegal states unrepresentable, and separating pure logic from effects are all directly applicable in TypeScript, Rust, Kotlin and Swift.
Haskell, OCaml or Rust for functional programming?
Haskell for purity, laziness and the most expressive type system — the best language for learning the ideas. OCaml for strict evaluation, faster compilation and a more predictable performance model. Rust if you want algebraic data types and pattern matching with systems-level control and much better job prospects.
How bad are the error messages?
Historically poor, particularly for type class resolution failures, where a small mistake could produce a page of output. They have improved substantially in recent GHC releases, and the Haskell Language Server showing inferred types inline removes much of the guesswork.


