Learn Haskell Part 7: A real, useful application with IO Monad
Talking with the real world
For now, all we did was using Haskell as a "calculator" language. Only staying in ghci evaluating expressions manualy. But we never interact with the user, nor with the system in general. So what, Haskell is useless after all ? No, of course not. We can totaly do these things in Haskell, and the way to do it is through ... a Monad ! Yes, the language encapsulated the "context" of values coming from the "real world" in a monad. This monad is called IO (for Input Output).
What really is the IO monad
Let's start by looking at some intresting monadic functions that live in IO:
-- work with standard input/output
Let's test getLine. It says it's a IO String, it means that it's a "real world program" that produces a String. And by its name, we understand that this String comes from the user's input. Fortunately, ghci is kind to us and executes IO actions instead of just trying to show them to us, so a IO String will print the produced String.
λ> getLine
Oh no, it hanged... Don't worry, all getLine is waiting for, is an input from you. Give it some text:
λ> getLine
hello ?
"hello ?"
λ>
See ? It executed the action and printed the result !
It's funny, because usualy, these actions are procedures that you call. In Python, the same code would be:
But in Haskell, it's just a value, getLine is not a function, it's just the computed value. The only hint that tells us "oh it may be a program that does weird things and returns a String" is because the value is wrapped in IO.
Let's try another function. This one is a bit more difficult to call because we need to load a package:
λ> :set -package random
package flags have changed, resetting and loading new packages...
λ> :m + System.Random
λ> :t randomIO
λ> randomIO
184628541959586994
λ> randomIO
1874965469327142086
λ> randomIO
8463203572085198601
λ>
Do you see that everytime we want to print the value of randomIO we get a different result ? Despite me always talking about immutability ? This is because the value is not really there. randomIO is "a program that computes a random object". This is not directly the computed object itself.
Now we see what the IO monad is all about. This is the context of programs that compute values of some type. And I say program exactly like a JavaScript, Python, or even C program. Something that can do side effects for example. This is why they can "return" different values if you "call" them repeatedly.
Chained IO action
Since they are Monads, we can chain these programs together ! Let's write a program that asks us for our name, and outputs "Hello [yourname]":
λ> greeting = getLine >>= (\name -> putStrLn ("Hello " ++ name))
λ> :t greeting
λ> greeting
lily
Hello lily
λ>
And we have it, a real world program, after 3768 lines of markdown 😅.

The unit type
Sometimes, programs don't produce values, they just modify their environment. They "perform some action". It can be "sending something over a TCP connection", "write to a file", "draw on the screen"... All these actions don't have to return anything. But you can't "return nothing" in Haskell. And IO action still needs to know "which type is the value we produce". But there really is no value, so how to we encode this in the type system ?
By creating a type that has only one possible value. This type carries no information, because it has only one possible value. And information is about different possibilities. A bit is either 1 or 0, the currents flows or not...
This type that carries no information is called the unit type. Also often written (). This is like a tuple but with no element. An empty object. You can define it as:
data () = ()
This unit type can encode this absence of (useful) value, and it is used, among other uses, to tell that a IO () action does something, but creates no result. This is why getLine has this type:
This function takes a String and outputs it in the terminal. Even our program greeting has type IO (), because this program just talks with the real world. It asks us for some input, and writes back some output in the terminal.
Prettier chaining
As you saw with the different monads we played with, the "then" operator (>>=) is very powerful and is the keystone of Haskell. This is why Haskell logo is a mix of a lambda and >>=.
But if I wanted to do some pretty basic calculator asking for two numbers and an operation, and print the result using only (>>=), it would be very ugly.
calculator =
getLine >>=
\l1 -> getLine >>=
\l2 -> getLine >>=
\l3 ->
let
n1 = read l1 :: Int;
n2 = read l2 :: Int;
in case l3 of
"+" -> print (n1 + n2)
"-" -> print (n1 - n2)
"*" -> print (n1 * n2)
"/" -> print (div n1 n2)
_ -> putStrLn ("Invalid operation " ++ show n1 ++ " " ++ l3 ++ " " ++ show n2)
And everytime we want to do a new action after another, we have to use (>>=) in some weird lambda chain. This is just ugly. Fortunately, Haskell have a very nice syntactic sugar. The do notation. Simply write do to begin a do block, and then write your actions one after the other:
writeALongText = do
putStrLn "char*lie;"
putStrLn " double time, me= !0XFACE,"
putStrLn " not; int rested, get, out;"
Under the hood, Haskell will chain them with (>>=), but in this form it is way easier to understand what is happening ! It is even as readable as imperative programming. But how do we get the values returned by monadic actions in this notation ? Like this:
greeting = do
putStrLn "Hello, who are you ?"
-- the <- arrow tells Haskell we want to bind
-- the value produced by getLine to
name <- getLine
-- we can define intermediate values with pure functions
-- it is like "let ... in ..." but "as a step in the program"
-- be careful though, it still does not mean Haskell is imperative
-- and everything defined in these `let` steps is still just a binding
let msg name = "Oh hello " ++ name ++ " it's a pleasure"
theMessage = msg name
-- we can use this value later
putStrLn theMessage
Now how would you write a program that asks for two numbers, an operation, and prints the result of the operation ?
calculator = do
l1 <- getLine
l2 <- getLine
op <- getLine
let n1 = read l1 :: Int
n2 = read l2 :: Int
case op of
"+" -> print (n1 + n2)
"-" -> print (n1 - n2)
"*" -> print (n1 * n2)
"/" -> print (div n1 n2)
_ -> putStrLn ("Invalid operation " ++ show n1 ++ " " ++ op ++ " " ++ show n2)
That's it, you know how to write elegant complex monadic operations now !
The REPL
Finaly we can use all our knowledge to create this program. We just need a program that continuously asks for an input, parse it as an RPN expression, and evaluates it, printing the stack each time it evaluates a line.
We can use getLine to read an input line, we can use evalRPN to compute the written program, and then we check for an error.
rpnREPL = loop []
where
loop stack = do
-- TODO: implement the loop
rpnREPL = loop []
where
loop stack = do
putStr "> "
line <- getLine
-- here, the >>= is not for IO, but for Result, we work on the interpretor's monad
let result = readRPNExpression line >>= evalRPNExpression stack
case result of
Left error -> putStrLn ("Error: " ++ error)
Right stack -> do
putStrLn (foldl (\str num -> str ++ ":" ++ show num) "" stack)
loop stack
Compiling a real Haskell program
Now that we finaly have a running program, we can compile it to an application instead of just running it in ghci. We just have to create an object named main, of type IO (), just like in many languages this object will be our entrypoint, the thing that gets executed when the program runs.
We can just write
main = rpnREPL
Because the types match 😉. Now in your console, simply run
e.g. for me:
And then try it by running the produced executable:
> 1
> dup
> *
> drop
> drop
> blerp