Haskell Language Getting started with Haskell Language Hello, World!


A basic "Hello, World!" program in Haskell can be expressed concisely in just one or two lines:

main :: IO ()
main = putStrLn "Hello, World!"

The first line is an optional type annotation, indicating that main is a value of type IO (), representing an I/O action which "computes" a value of type () (read "unit"; the empty tuple conveying no information) besides performing some side effects on the outside world (here, printing a string at the terminal). This type annotation is usually omitted for main because it is its only possible type.

Put this into a helloworld.hs file and compile it using a Haskell compiler, such as GHC:

ghc helloworld.hs

Executing the compiled file will result in the output "Hello, World!" being printed to the screen:

Hello, World!

Alternatively, runhaskell or runghc make it possible to run the program in interpreted mode without having to compile it:

runhaskell helloworld.hs

The interactive REPL can also be used instead of compiling. It comes shipped with most Haskell environments, such as ghci which comes with the GHC compiler:

ghci> putStrLn "Hello World!"
Hello, World!

Alternatively, load scripts into ghci from a file using load (or :l):

ghci> :load helloworld

:reload (or :r) reloads everything in ghci:

Prelude> :l helloworld.hs 
[1 of 1] Compiling Main             ( helloworld.hs, interpreted )

<some time later after some edits>

*Main> :r
Ok, modules loaded: Main.


This first line is a type signature, declaring the type of main:

main :: IO ()

Values of type IO () describe actions which can interact with the outside world.

Because Haskell has a fully-fledged Hindley-Milner type system which allows for automatic type inference, type signatures are technically optional: if you simply omit the main :: IO (), the compiler will be able to infer the type on its own by analyzing the definition of main. However, it is very much considered bad style not to write type signatures for top-level definitions. The reasons include:

  • Type signatures in Haskell are a very helpful piece of documentation because the type system is so expressive that you often can see what sort of thing a function is good for simply by looking at its type. This “documentation” can be conveniently accessed with tools like GHCi. And unlike normal documentation, the compiler's type checker will make sure it actually matches the function definition!

  • Type signatures keep bugs local. If you make a mistake in a definition without providing its type signature, the compiler may not immediately report an error but instead simply infer a nonsensical type for it, with which it actually typechecks. You may then get a cryptic error message when using that value. With a signature, the compiler is very good at spotting bugs right where they happen.

This second line does the actual work:

main = putStrLn "Hello, World!"

If you come from an imperative language, it may be helpful to note that this definition can also be written as:

main = do {
   putStrLn "Hello, World!" ;
   return ()

Or equivalently (Haskell has layout-based parsing; but beware mixing tabs and spaces inconsistently which will confuse this mechanism):

main = do
    putStrLn "Hello, World!"
    return ()

Each line in a do block represents some monadic (here, I/O) computation, so that the whole do block represents the overall action comprised of these sub-steps by combining them in a manner specific to the given monad (for I/O this means just executing them one after another).

The do syntax is itself a syntactic sugar for monads, like IO here, and return is a no-op action producing its argument without performing any side effects or additional computations which might be part of a particular monad definition.

The above is the same as defining main = putStrLn "Hello, World!", because the value putStrLn "Hello, World!" already has the type IO (). Viewed as a “statement”, putStrLn "Hello, World!" can be seen as a complete program, and you simply define main to refer to this program.

You can look up the signature of putStrLn online:

putStrLn :: String -> IO ()
-- thus,
putStrLn (v :: String) :: IO ()

putStrLn is a function that takes a string as its argument and outputs an I/O-action (i.e. a value representing a program that the runtime can execute). The runtime always executes the action named main, so we simply need to define it as equal to putStrLn "Hello, World!".