How to Write “Hello World” in Haskell
In this article we're going to write the easiest program we can in the Haskell programming language. We're going to write a simple example program that prints "Hello World!" to the console. It's such a simple program that we can do it in one line! But it's still the first thing you should do when starting a new programming language. Even with such a simple program there are several details we can learn about writing a Haskell program. Here's a quick table of contents if you want to jump around!
- Writing Haskell "Hello World"
- The Simplest Way to Run the Code
- Functional Programming and Types
- Requirements of an Executable Haskell Program
- Using the GHC Compiler
- Using GHCI - The Haskell Interpreter
- A Closer Look at Our Types
- Compilation Errors
- A Quick Look At Type Classes
- Echo - Another Example Program
- A Complete Introduction to the Haskell Programming Language
Now let's get started!
Writing Haskell "Hello World"
To write our "Haskell Hello World" program, we just need to open a file named 'HelloWorld.hs' in our code editor and write the following line:
main = putStrLn "Hello World!"
This is all the code you need! With just this one line, there's still another way you could write it. You could use the function 'print' instead of 'putStrLn':
main = print "Hello World!"
These programs will both accomplish our goal, but their behavior is slightly different! But to explore this, we first need to run our program!
The Simplest Way to Run the Code
Hopefully you've already installed the Haskell language tools on your machine. The old way to do this was through Haskell Platform, but now you should use GHCup. You can read our Startup Guide for more instructions on that! But assuming you've installed everything, the simplest way to run your program is to use the 'runghc' command on your file:
>> runghc HelloWorld.hs
With the first version of our code using 'putStrLn', we'll see this printed to our terminal:
Hello World!
If we use 'print' instead, we'll get this output:
"Hello World!"
In the second example, there are quotation marks! To understand why this is, we need to understand a little more about types, which are extremely important in Haskell code.
Functional Programming and Types
Haskell is a functional programming language with a strong, static type system. Even something as simple as our "Hello World" program is comprised of expressions, and each of these expressions has a type. For that matter, our whole program has a type!
In fact, every Haskell program has the same type: 'IO ()'. The IO type signifies any expression which can perform Input/Output activities, like printing to the terminal and reading user input. Most functions you write in Haskell won't need to do these tasks. But since we're printing, we need the IO signifier. The second part of the type is the empty tuple, '()'. This is also referred to as the "unit type". When used following 'IO', it is similar to having a 'void' return value in other programming languages.
Now, our 'main' expression signifies our whole program, and we can explicitly declare it to have this type by putting a type signature above it in our code. We give the expression name, two colons, and then the type:
main :: IO ()
main = putStrLn "Hello World!"
Our program will run the same with the type signature. We didn't need to put it there, because GHC, the Haskell compiler, can usually infer the types of expressions. With more complicated programs, it can get stuck without explicit type signatures, but we don't have to worry about that right now.
Requirements of an Executable Haskell Program
Now if we gave any other type to our main function, we won't be able to run our program! Our file is supposed to be an entry point - the root of an executable program. And Haskell has several requirements for such files.
These files must have an expression named 'main'. This expression must have the type 'IO ()'. Finally, if we put a module name on our code, that module name should be Main. Module names go at the top of our file, prefaced by "module", and followed by the word "where". Here's how we can explicitly declare the name of our module:
module Main where
main :: IO ()
main = putStrLn "Hello World!"
Like the type signature on our function 'main', GHC could infer the module name as well. But let's try giving it a different module name:
module HelloWorld where
main :: IO ()
main = putStrLn "Hello World!"
For most Haskell modules you write, using the file name (minus the '.hs' extension) IS how you want to name the module. But runnable entry point modules are different. If we use the 'runghc' command on this code, it will still work. However, if we get into more specific behaviors of GHC, we'll see that Haskell treats our file differently if we don't use 'Main'.
Using the GHC Compiler
Instead of using 'runghc', a command designed mainly for one-off files like this, let's try to compile our code more directly using the Haskell compiler. Suppose we have used HelloWorld as the module name. What files does it produce when we compile it with the 'ghc' command?
>> ghc HelloWorld.hs
[1 of 1] Compiling HelloWorld ( HelloWorld.hs, HelloWorld.o )
>> ls
HelloWorld.hi HelloWorld.hs HelloWorld.o
This produces two output files beside our source module. The '.hi' file is an interface file. The '.o' file is an object file. Unfortunately, neither of these are runnable! So let's try changing our module name back to Main.
module Main where
main :: IO ()
main = putStrLn "Hello World!"
Now we'll go back to the command line and run it again:
>> ghc HelloWorld.hs
[1 of 2] Compiling Main ( HelloWorld.hs, HelloWorld.o )
[2 of 2] Linking HelloWorld
>> ls
HelloWorld HelloWorld.hi HelloWorld.hs HelloWorld.o
This time, things are different! We now have two compilation steps. The first says 'Compiling Main', referring to our code module. The second says 'Linking HelloWorld'. This refers to the creation of the 'HelloWorld' file, which is executable code! (On Windows, this file will be called 'HelloWorld.exe'). We can "run" this file on the command line now, and our program will run!
>> ./HelloWorld
Hello World!
Using GHCI - The Haskell Interpreter
Now there's another simple way for us to run our code. We can also use the GHC Interpreter, known as GHCI. We open it with the command 'ghci' on our command line terminal. This brings us a prompt where we can enter Haskell expressions. We can also load code from our modules, using the ':load' command. Let's load our hello world program and run its 'main' function.
>> ghci
GHCI, version 9.4.7: https://www.haskell.org/ghc/ :? for help
ghci> :load HelloWorld
[1 of 2] Compiling Main ( HelloWorld.hs, interpreted )
ghci> main
Hello World!
If we wanted, we could also just run our "Hello World" code in the interpreter itself:
ghci> putStrLn "Hello World!"
Hello World!
It's also possible to assign our string to a value and then use it in another expression:
ghci> let myString = "Hello World!"
ghci> putStrLn myString
Hello World!
A Closer Look at Our Types
A very useful function of GHCI is that it can tell us the types of our expressions. We just have to use the ':type' command, or ':t' for short. We have two expressions in our Haskell program: 'putStrLn', and "Hello World!". Let's look at their types. We'll start with "Hello World!":
ghci> :type "Hello World!"
"Hello World!" :: String
The type of "Hello World!" itself is a 'String'. This is the name given for a list of characters. We can look at the type of an individual character as well:
ghci> :type 'H'
'H' :: Char
What about 'putStrLn'?
ghci> :t putStrLn
putStrLn :: String -> IO ()
The type for 'putStrLn' looks like 'String -> IO ()'. Any type with an arrow in it ('->') is a function. It takes a 'String' as an input and it returns a value of type 'IO ()', which we've discussed. In order to apply a function, we place its argument next to it in our code. This is very different from other programming languages, where you usually need parentheses to apply a function on arguments. Once we apply a function, the type of the resulting expression is just whatever is on the right side of the arrow. So applying our string to the function 'putStrLn', we get 'IO ()' as the resulting type!
ghci> :t putStrLn "Hello World!"
putStrLn "Hello World!" :: IO ()
Compilation Errors
For a different example, let's see what happens if we try to use an integer with 'putStrLn':
ghci> putStrLn 5
No instance for (Num String) arising from the literal '5'
The 'putStrLn' function only works with values of the 'String' type, while 5 has a type more like 'Int'. So we can't use these expressions together.
A Quick Look At Type Classes
However, this is where 'print' comes in. Let's look at its type signature:
ghci> :t print
print :: Show a => a -> IO ()
Unlike 'putStrLn', the 'print' function takes a more generic input. A "type class" is a general category describing a behavior. Many different types can perform the behavior. One such class is 'Show'. The behavior is that Show-able items can be converted to strings for printing. The 'Int' type is part of this type class, so we can use 'print' with it!
ghci> print 5
5
When use 'show' on a string, Haskell adds quotation marks to the string. This is why it looks different to use 'print' instead of 'putStrLn' in our initial program:
ghci> print "Hello World!"
"Hello World!"
Echo - Another Example Program
Our Haskell "Hello World" program is the most basic example of a program we can write. It only showed one side of the input/output equation. Here's an "echo" program, which first waits for the user to enter some text on the command line and then prints that line back out:
main :: IO ()
main = do
input <- getLine
putStrLn input
Let's quickly check the type of 'getLine':
ghci> :t getLine
getLine :: IO String
We can see that 'getLine' is an IO action returning a string. When we use the backwards arrow '<-' in our code, this means we unwrap the IO value and get the result on the left side. So the type of 'input' in our code is just 'String', meaning we can then use it with 'putStrLn'! Then we use the 'do' keyword to string together two consecutive IO actions. Here's what it looks like to run the program. The first line is us entering input, the second line is our program repeating it back to us!
>> runghc Echo.hs
I'm entering input!
I'm entering input!
A Complete Introduction to the Haskell Programming Language
Our Haskell "Hello World" program is the most basic thing you can do with the language. But if you want a comprehensive look at the syntax and every fundamental concept of Haskell, you should take our beginners course, Haskell From Scratch.
You'll get several hours of video lectures, plus a lot of hands-on experience with 100+ exercise problems with automated testing.
All-in-all, you'll only need 10-15 hours to work through all the material, so within a couple weeks you'll be ready for action! Read more about the course here!