Skip to content
Haskell logo

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.

  1. 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.

  2. 1990

    Haskell 1.0

    The first report is published, establishing lazy evaluation, purity and type classes as the language's defining commitments.

  3. 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.

  4. 2003

    The Haskell 98 report

    A stable, conservative standard is published, giving the language a fixed target while GHC continues to explore beyond it.

  5. 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.

  6. 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.

  7. 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.

Types, pattern matching and totality
-- 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
There is no null and no exception here — absence is a value of a type the compiler tracks. Compile with `-Wall`: incomplete pattern matches are warnings by default rather than errors, and turning them on is essential.
Effects appear in the type
-- 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 cfg
The pattern that matters is the split: a thin IO shell reads and writes, and all the logic lives in pure functions that need no mocks to test. Haskell enforces this separation rather than leaving it to discipline.
Type classes and higher-kinded abstraction
class 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
`traverse` is a good illustration of what higher-kinded types buy: one function turns a list of possibly-failing computations into a possibly-failing list, and works unchanged over Maybe, Either, IO or any other applicative.
Laziness, and the space leak it can cause
-- 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 }
This is the canonical Haskell space leak. `foldl` and `foldl'` differ by one character and by whether your program survives a large input — which is why understanding evaluation order is not optional.

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. 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. 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. 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. 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. 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.

ToolWhat it does
GHCThe compiler — effectively the language implementation, and a research vehicle in its own right
GHCupInstalls and manages GHC, Cabal, Stack and the language server; the modern starting point
Cabal / StackBuild and dependency management; Stack adds curated, reproducible snapshots
Hackage / StackageThe package archive, and a curated set of versions known to build together
Haskell Language ServerIDE support — completion, types on hover, refactoring; a large improvement in approachability
AesonThe standard JSON library, with generic derivation from data types
ServantWeb APIs described as types, generating server, client and documentation from one definition
QuickCheck / HspecProperty-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 libraries

Frequently 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.