100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Your First Haskell Program

Writing, compiling, and running a first Haskell program, from a one-line Hello World to a small interactive do-notation example.

FoundationsBeginner8 min readJul 10, 2026
Analogies

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.

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

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

haskell
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.hs compiles to a native, dependency-free executable; stack build does the same inside a project.
  • ghci Main.hs gives a fast REPL iteration loop, with :reload picking up source edits.
  • do-notation sequences multiple IO actions strictly in the order they're written.
  • x <- action binds an IO result to a name; let x = expr binds a pure value with no effect.
  • read can crash on malformed input; Text.Read.readMaybe is the safer alternative.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#HaskellStudyNotes#YourFirstHaskellProgram#Haskell#Program#Hello#World#StudyNotes#SkillVeris#ExamPrep