Haskell Reference
Free reference guide: Haskell Reference
About Haskell Reference
The Haskell Reference is a comprehensive, searchable guide to the Haskell purely functional programming language. It is organized into six categories that reflect Haskell's unique design philosophy: Basics (let/where bindings, if-then-else as an expression, guards, pattern matching, case expressions, list comprehensions, list operations, and lambda functions), Types (type annotations, algebraic data types with data, record syntax, newtype wrappers, and type aliases), Functions (map/filter/foldr, function composition with (.), the $ application operator, currying, zipWith/concatMap), Monads (Maybe, Either, do notation, the >>= bind operator, and return/pure), Type Classes (class/instance definitions, deriving Show/Eq/Ord/Enum/Bounded, Functor/Applicative), and IO (putStrLn/print, getLine/readLn, readFile/writeFile/appendFile, IORef for mutable state).
Functional programming learners, Haskell practitioners building production services with libraries like Servant or Yesod, and computer science students studying type theory and category theory are the primary audience. This reference is particularly useful as a desk companion when working through real Haskell code, since Haskell's syntax differs significantly from imperative languages: every construct is an expression that returns a value, functions are curried by default, and monads provide a structured way to handle side effects, optional values, and error propagation without exceptions.
The reference highlights Haskell-specific patterns that developers from other languages commonly find challenging: the distinction between let and where for local bindings, guard syntax as an alternative to deeply nested if-else chains, the difference between fmap and <*> (Applicative), how do notation desugars into >>= (bind) chains, and the use of IORef as Haskell's mechanism for mutable state within the IO monad. Data.IORef is covered alongside the pure functional data structures to give a complete picture of state management in Haskell.
Key Features
- Pattern matching: function definition by cases (factorial 0 = 1), list deconstruction with (x:xs), and wildcards with underscore
- Guard syntax for multi-way conditionals (| x < 18.5 = ...) and list comprehensions with predicates ([x^2 | x <- [1..10], even x])
- Algebraic data types with data: sum types (Circle | Rectangle), product types, record syntax with named fields, and deriving clauses
- Type system features: type annotations (::), newtype for zero-overhead wrappers, type aliases, and function type signatures with ->
- Higher-order functions: map, filter, foldr, foldl, zipWith, concatMap, function composition (.) and the $ application operator
- Currying and partial application: every multi-arg function is automatically curried; partial application creates new functions (e.g., add5 = add 5)
- Monad patterns: Maybe for safe computation chains, Either for error propagation with messages, do notation for sequencing, and >>= bind with lambda
- Type classes: defining class/instance, automatic deriving of Show/Eq/Ord/Enum/Bounded, Functor (fmap) and Applicative (<*>) for lifted operations
Frequently Asked Questions
What is pattern matching in Haskell and how does it differ from switch statements?
Pattern matching in Haskell allows you to define a function by providing multiple equations, each matching a different input shape. For example, factorial 0 = 1 and factorial n = n * factorial (n-1) define two cases. Unlike switch statements, Haskell patterns can match on constructors (Just x, Nothing), list shapes (x:xs, []), and tuples — and the compiler warns if patterns are non-exhaustive.
What are guards and when should I use them instead of if-then-else?
Guards are a multi-way conditional syntax attached to function clauses, written with | and =. They are cleaner than nested if-then-else when you have three or more conditions. For example, the BMI classifier with | x < 18.5 = "underweight" | x < 25.0 = "normal" | otherwise = "overweight" is much more readable than nested ifs.
What is the difference between Maybe and Either monads?
Maybe encodes computations that might fail without a reason: Just x for success and Nothing for failure. Either adds a failure message: Right x for success and Left "error message" for failure. Use Maybe when the failure case needs no explanation (e.g., dictionary lookup), and Either when you need to propagate a descriptive error message to the caller.
How does do notation relate to the >>= bind operator?
do notation is syntactic sugar for monadic bind (>>=). The line "x <- action" in a do block desugars to "action >>= \x -> ..." and "action" on its own line desugars to "action >> ...". For IO monad, do notation reads like imperative code while remaining purely functional under the hood.
What is the difference between fmap and <*> in Haskell?
fmap (the Functor operation) lifts a pure function into a context: fmap (+1) (Just 5) = Just 6. <*> (the Applicative operation) applies a function that is itself inside a context to a value inside a context: Just (+3) <*> Just 5 = Just 8. Think of fmap as mapping and <*> as function application inside a box.
What are Haskell type classes and how do they differ from OOP interfaces?
Type classes define a set of operations that a type must support, similar to interfaces in OOP. But they are more powerful: you can add type class instances for existing types without modifying them (retroactive implementation), and the compiler selects the correct implementation at compile time based on types. Common type classes include Show (toString), Eq (equality), Ord (ordering), and Functor (mapping).
How does Haskell handle mutable state if it is purely functional?
Haskell's pure functions cannot have side effects, but the IO monad provides a controlled way to perform I/O and manage mutable state. IORef is the standard mutable reference type: newIORef 0 creates a reference, modifyIORef ref (+1) updates it, and readIORef ref reads it. All these operations return IO actions, ensuring mutations are tracked by the type system.
What is currying and how does partial application work in Haskell?
In Haskell, every function that takes multiple arguments is automatically curried — it actually takes one argument and returns a function that takes the next. This means you can partially apply a function by providing fewer arguments than it expects: add5 = add 5 creates a new function that adds 5 to any number. map (add 10) [1,2,3] uses partial application to add 10 to each list element.