Hello World in Haskell
Your first Haskell program - the classic Hello World example with Docker setup using GHC
Every programming journey starts with Hello World. Let’s write our first Haskell program using GHC (Glasgow Haskell Compiler), the standard Haskell implementation.
The Code
Create a file named hello.hs:
| |
That’s it! Just two lines for a complete Haskell program.
Understanding the Code
Let’s break down each part:
The Type Signature
| |
main- The name of the function (the entry point)::- Read as “has type”IO ()- The type of the function
The type IO () means:
IO- This function performs input/output (a side effect)()- It returns unit (similar tovoidin other languages)
The Function Definition
| |
main =- Defines whatmaindoesputStrLn- A function that prints a string followed by a newline"Hello, World!"- The string argument to print
Why Two Lines?
In Haskell, type signatures are optional - the compiler can usually infer them:
| |
However, explicit type signatures are considered good practice because they:
- Document your intent
- Help catch errors
- Make code easier to understand
Running with Docker
The easiest way to run Haskell without installing anything locally is with Docker:
| |
Understanding the Docker Command
docker run --rm- Run a container and remove it when done-v $(pwd):/app- Mount the current directory to/appin the container-w /app- Set the working directory to/apphaskell:9.6- Use the official Haskell 9.6 Docker imagerunghc hello.hs- Interpret and run the Haskell file
Compiling vs Interpreting
The runghc command interprets your program (good for quick testing). For compiled code:
| |
This compiles to a native executable, which runs much faster for real programs.
Expected Output
Hello, World!
Alternative Approaches
Haskell offers several ways to output text:
Using print
| |
Note: print adds quotes around strings in output: "Hello, World!"
Using putStr (no newline)
| |
Using do notation
For multiple actions:
| |
Using the » operator
Sequence actions without binding results:
| |
Understanding IO in Haskell
Haskell is a pure functional language - functions can’t have side effects. So how does putStrLn print to the screen?
The IO Type
IO is a type that represents a computation that may perform side effects:
| |
When you write putStrLn "Hello", you’re not executing a print. You’re creating a value that represents “the action of printing Hello.”
main is Special
The Haskell runtime executes whatever IO action is bound to main. Only main (and functions called by main) actually perform side effects.
| |
Sequencing with do
The do notation lets you sequence multiple IO actions:
| |
This desugars to uses of the >> and >>= operators:
| |
Function Application
Haskell uses space for function application, not parentheses:
| |
When you need to group expressions, use parentheses:
| |
Type Inference
Haskell can usually figure out types automatically:
| |
You can ask GHCi what type something has:
ghci> :type putStrLn
putStrLn :: String -> IO ()
ghci> :type "Hello"
"Hello" :: String
Common Beginner Mistakes
Missing the Type Signature Context
| |
Forgetting IO is a Functor
| |
Wrong String Syntax
| |
Installing Haskell Locally
If you prefer to run Haskell without Docker:
Using GHCup (Recommended):
| |
This installs:
- GHC (the compiler)
- Cabal (package manager)
- Stack (build tool)
- HLS (language server for editors)
macOS with Homebrew:
| |
Ubuntu/Debian:
| |
After installation:
| |
The REPL: GHCi
Haskell has an excellent interactive environment:
$ ghci
GHCi, version 9.6.1: https://www.haskell.org/ghc/
Prelude> putStrLn "Hello, World!"
Hello, World!
Prelude> 2 + 2
4
Prelude> :type putStrLn
putStrLn :: String -> IO ()
Prelude> :quit
Useful GHCi commands:
:type(:t) - Show the type of an expression:info(:i) - Show info about a type or function:load(:l) - Load a file:reload(:r) - Reload the current file:quit(:q) - Exit GHCi
A Bit of History
Haskell was designed by a committee of researchers in 1990 to unify the various lazy functional languages that existed at the time. It was named after Haskell Curry, a mathematician whose work on combinatory logic laid the foundation for functional programming.
The language has evolved significantly:
- 1990: Haskell 1.0 released
- 1996: Haskell 1.3 added monadic I/O
- 1998: Haskell 98 standardized
- 2010: Haskell 2010 (current standard)
GHC, developed primarily at the University of Glasgow, has become the standard implementation and continues to add experimental features that often become de facto standards.
Why Haskell for Hello World?
Even in this simple program, you see Haskell’s philosophy:
- Types First - The type signature documents what
maindoes - Purity Matters - IO is explicitly marked in the type
- Simplicity - No boilerplate, just the essential logic
- Safety - The type system ensures you can’t accidentally mix pure and impure code
Next Steps
Continue to Functions and Types to learn about Haskell’s powerful type system and how to define your own functions.
Key Takeaways
main :: IO ()declares an IO action as the entry pointputStrLnprints a string with a newline- Type signatures are optional but recommended
runghcinterprets Haskell;ghccompiles it- IO is a type that represents side effects
- Function application uses space, not parentheses
- do notation sequences multiple IO actions