Hello, World: The main Entry Point
Every standalone Haskell executable needs a main binding of type IO (), the single entry point the runtime invokes when the compiled program starts -- the empty tuple () (pronounced 'unit') signals that main produces no meaningful result value, only the side effect of running. A minimal Main.hs containing just main = putStrLn "Hello, World!" is a complete, compilable program: putStrLn :: String -> IO () takes a string and returns an IO action that, when executed by the runtime, prints that string followed by a newline.
Cricket analogy: The toss, as the mandatory single starting event every match must have before anything else can happen, mirrors main :: IO () being the one required entry point the Haskell runtime always looks for first.
-- Main.hs
main :: IO ()
main = putStrLn "Hello, World!"
Compiling and Running
Compiling with ghc Main.hs (or, in a Stack project, stack build) produces a native executable that runs without any further dependency on GHC being installed on the target machine, unlike interpreted languages that need their runtime present at execution time. During development, though, the much faster iteration loop is GHCi: running ghci Main.hs loads the file and drops you into a REPL where you can call main directly to test it, redefine functions on the fly, and use :reload (or :r) after editing the source file to pick up changes without restarting the whole session.
Cricket analogy: A finished, printed scorecard that stands on its own without needing the live scoring software running mirrors a compiled Haskell executable running independently of GHC, while checking a live app during the match mirrors the faster GHCi iteration loop.
# Compile to a native executable and run it
ghc Main.hs
./Main
# Hello, World!
# Or iterate quickly in GHCi
ghci Main.hs
ghci> main
Hello, World!
ghci> :reload
ghc -O2 Main.hs enables full optimization, which typically matters for CPU-bound programs; for a first Hello World it's unnecessary, but it's a good habit to know before writing anything performance-sensitive.
A Small Interactive Program with do-notation
Once main needs to do more than one thing, do-notation sequences multiple IO actions in order, top to bottom; x <- getLine binds the result of an IO String action to the name x for use in later lines, while a plain let x = expr inside a do block binds a pure value with no effect involved. A small but common first-program pattern reads a name, echoes a greeting, and reads a number to double, combining getLine, read, putStrLn, and show (which converts most types to their String representation, the inverse of read).
Cricket analogy: A pre-match routine performed in a strict, fixed order -- pitch inspection, then toss, then team announcement -- mirrors do-notation sequencing IO actions top to bottom in a Haskell main.
main :: IO ()
main = do
putStrLn "What's your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ "!")
putStrLn "Give me a number to double:"
numText <- getLine
let n = read numText :: Int
putStrLn ("Doubled: " ++ show (n * 2))
read throws a runtime exception on malformed input -- typing 'abc' when a number is expected crashes this program. Robust input parsing uses Text.Read.readMaybe, which returns Maybe Int instead of crashing, letting you handle bad input explicitly with pattern matching.
- Every standalone Haskell program needs a
main :: IO ()entry point. putStrLn :: String -> IO ()prints a string followed by a newline.ghc Main.hscompiles to a native, dependency-free executable;stack builddoes the same inside a project.ghci Main.hsgives a fast REPL iteration loop, with:reloadpicking up source edits.do-notation sequences multiple IO actions strictly in the order they're written.x <- actionbinds an IO result to a name;let x = exprbinds a pure value with no effect.readcan crash on malformed input;Text.Read.readMaybeis the safer alternative.
Practice what you learned
1. What type must `main` have in a standalone Haskell program?
2. What does `ghc Main.hs` produce?
3. What does `x <- getLine` do inside a `do` block?
4. Why is `read` risky when parsing user input?
5. What is the safer alternative to `read` for parsing potentially invalid input?
Was this page helpful?
You May Also Like
Installing GHC and Stack
How to set up a working Haskell toolchain -- GHC, Cabal, Stack, and HLS -- using GHCup, and the daily commands you'll use to build and run projects.
Functions in Haskell
How to define, call, curry, and locally scope functions in Haskell, including lambdas and where/let bindings.
Values and Types in Haskell
How Haskell's static type system, primitive types, immutable value bindings, and type inference work together, illustrated with GHCi examples.
The IO Monad
Understand how Haskell models side effects with the IO monad -- IO values as pure descriptions of effects, do-notation sequencing, why you can't escape IO into pure code, and how to keep most logic pure.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics