How Haskell Code Runs
Prerequisites: Your first program
When you compile or run a Haskell file, GHC works in stages:
- Parse the file. Syntax errors — like bad indentation — are reported now.
- Type-check every expression. Most mistakes are caught here, before the program ever runs.
- Compile to native machine code (with
ghc) or to bytecode that the interpreter runs (withrunghcandghci). - Run
main, evaluating expressions lazily as their values are needed.
A type error
main :: IO ()
main = do
putStrLn "start"
putStrLn ("Total: " ++ 42)
main.hs:4:26: error: [GHC-39999]
* No instance for `Num [Char]' arising from the literal `42'
* In the second argument of `(++)', namely `42'
In the first argument of `putStrLn', namely `("Total: " ++ 42)'
In a stmt of a 'do' block: putStrLn ("Total: " ++ 42)
|
4 | putStrLn ("Total: " ++ 42)
| ^^
Nothing ran — not even "start" — because the file failed to type-check.
++ joins two lists, and 42 is a number, not a string. The fix is
"Total: " ++ show 42. Read error messages carefully: they give the
file, line, and column (4:26), and a code like GHC-39999 you can
look up on errors.haskell.org.
A runtime error
Type checking can't catch everything:
main :: IO ()
main = do
putStrLn "start"
print (head ([] :: [Int]))
putStrLn "never reached"
main.hs:4:10: warning: [GHC-63394] [-Wx-partial]
In the use of `head'
(imported from Prelude, but defined in GHC.Internal.List):
"This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use "Data.List.NonEmpty"."
|
4 | print (head ([] :: [Int]))
| ^^^^
start
main.hs: Uncaught exception ghc-internal:GHC.Internal.Exception.ErrorCall:
Prelude.head: empty list
HasCallStack backtrace:
error, called at libraries\ghc-internal\src\GHC\Internal\List.hs:1969:3 in ghc-internal:GHC.Internal.List
errorEmptyList, called at libraries\ghc-internal\src\GHC\Internal\List.hs:96:11 in ghc-internal:GHC.Internal.List
badHead, called at libraries\ghc-internal\src\GHC\Internal\List.hs:90:28 in ghc-internal:GHC.Internal.List
head, called at main.hs:4:10 in main:Main
This time the program compiled — with a warning first — then "start"
printed, and the program crashed: head of an empty list has no answer.
The backtrace at the end shows the chain of calls that led to the
error, ending with your own code (main.hs:4:10). You'll learn to avoid
partial functions like head by using pattern matching and Maybe
instead — which is exactly what GHC's -Wx-partial warning suggests.
Purity in one sentence
Code outside IO can't print, read input, or change anything — it can
only compute a result from its arguments. That's why the compiler can
rearrange, share, and lazily evaluate it safely, and why functions are so
easy to test.