Edit: After writing this post I turned to reddit for advice on how to make this post better, and even after a complete re-write It still felt lacking. After asking about it at #haskell, merijn linked to this article which in my opinion explains this subject much better than mine. So you might want to read it before this post or even instead of.
Haskell is a purely functional language, but what does being "pure" mean?
The pure in purely functional means that Haskell enforces the separation between evaluating an expression and executing an expression.
An expression can be thought about simply as a value. In order to produce a value, an expression needs to be evaluated (or calculated).
While evaluated, expressions are stateless and cannot do anything other than compute a value and return it, and will also return the same value. Always. This also means it can't mutate a variable, read from file or write to standard output. We say that evaluating expressions is pure.
All expressions in Haskell are pure, which mean that we can evaluate any expression. But some expressions may need to do more than just being evaluated in order to do something useful. Some expressions needs to do other things in order to produce a meaningful value, this kind of expressions can be executed. Let's call this kind of expressions IO actions.
Producing value just by evaluating expressions is simpler and more straightforward in Haskell, while executing expressions requires more attention and can only happen at certain places. Most expressions in Haskell do not need to be executed in order to produce a meaningful value.
Haskell enforces this separation between pure expressions and IO actions using it's type system at compile time.
We can differentiate between pure expressions and IO actions by looking at the type of an expression. An IO action's type signature is slightly different from a pure function or value.
val1 :: Int val2 :: IO Int func1 :: Int -> Int func2 :: Int -> IO Int
func1 are pure - they don't need to be executed to produce an
Int, only evaluated.
func2 are IO actions - in order to produce an
Int they need to be executed. We can tell because they return an
IO _ type.
Let's say we want to write a program that reads an
Int from a user, and multiply it by a
Here is a naive attempt where we treat an
IO Int as equal to
Int, which means that when evaluating
IO Int we also execute it and produce an
readIntFromUser :: IO Int readIntFromUser = ... mul :: Int -> Int -> Int mul = (*) userInt :: IO Int userInt = readIntFromUser multiplier :: Int multiplier = 3 main = print (mul multiplier userInt)
Well, this might be a little bit problematic, because now we lost the ability to separate evaluation from execution.
Also, since the type of
Int -> Int -> Int, the type of
mul multiplier userInt is also an
Int, so now we don't know
if we are going to get the same value every time or not. So we can't replace the implementation of
mul by something that, for example,
Pure expressions allow us to do "programming algebra" like this without unexpected side-effects.
So, we will not treat an
IO Int as an
Int and thus retain the separation of evaluation and execution! (Means this is not valid Haskell code.)
Okay, so, how do we multiple a user read
Hmm. Perhaps we can use a function that can execute an IO action, thus converting
IO Int to
Int? Let's call a function like that
But now, it is also entirely possible to rewrite
multiplier like this:
multiplier :: Int multiplier = if execute userInt == 3 then 3 else 3
multiplier, in this case, still equals 3 but now it also reads user input, so it is not pure even though it's type is
Int and will return 3 every time!
So, converting an
IO a type to
a by executing it whenever we want is rejected. (Not valid Haskell either.)
Let's look at this from a different perspective, what if we could take the function we want to apply to the
IO Int value,
apply it to the
Int that would be calculated when executed,
and return a new
IO Int with the new value after applying the function? That way we can still keep the separation of pure expressions and IO actions
And indeed, we can. Using
fmap :: (a -> b) -> IO a -> IO b fmap = ... main :: IO () main = print (fmap (mul multiplier) userInt) -- ==> error: print cannot take IO Int.
Yes! We produced an IO action that will take a user input and will multiply it by
multiplier! But now we have a different problem,
IO Int, maybe it wants an
Int? Let's try the same
trick we just discovered on
main :: IO () main = fmap (print (fmap (mul multiplier) userInt)) -- ==> error: main type mismatch between IO () and IO (IO ())
Hmm, we were able to apply print to our computation, but print returns an
IO (), so now after using
main function has a type of
IO (IO ()) and not
As we said, IO actions are just like regular values that can be evaluated (in the same way a function can be evaluated). They can be returned from a function or stored in a data structure. It is just that if we want to produce a value from them, we have to execute them.
Another analogy that might be helpful is to think about IO actions as "plans" to produce a value. For example,
IO a action is a plan to produce a value of type
a. The plan itself is a regular, first class value.
But in order to produce the
a value of the plan, we need to execute it.
Sometimes we want to return an IO action to be executed later, but in this case, we just want to execute this IO action.
So, what if we could join two
IO together to one
IO so we can think of them as one plan to execute a value?
Apparently, we can. Using
join :: IO (IO a) -> IO a join = ... main :: IO () main = join (fmap (print (fmap (mul multiplier) userInt)))
Great! Now everything typechecks! But it looks like a lot of work. Can't we make it more concise?
bind :: (a -> IO b) -> IO a -> IO b bind f x = join (fmap f x) main :: IO () main = bind (print . mul multiplier) userInt
Now, what if, for example, We want to test this function by supplying a value (like -3), we can use
pure to create a "plan"
to produce a specific
Int. By the way, this IO action will not do anything beyond returning a value when executed, since we gave it a value to produce,
it doesn't need to do anything in order to produce it.
userInt :: IO Int userInt = pure (-3) main :: IO () main = bind (print . mul multiplier) userInt
Okay, But we still haven't figured out when to execute an IO action and how.
Let's decide that we execute
main, by executing the IO actions that compose it, be it one IO action or more that are composed with
bind, and in the order
they are sequenced (since you can't print without knowing what is the value the user entered). That way, we can still execute IO actions as much as we like,
but we will also be able to say "anything beyond this is pure".
A short summary
Let's stop for a moment and go over how we wanted to create and enforce the separation of pure code and IO actions and what we discovered in the process.
- We decided to use types to differentiate between expressions that can be executed and expressions that cannot. Expressions that can be executed has IO in their type.
- We decided that we don't want to treat
a- we don't want execution to happen on evaluation.
- We decided that we don't want to be able to convert
a(which means to execute it anywhere we want), and so once we are in IO context, we can't "escape" it.
- We decided that we can convert
pure, supplying a value to return on execution. (you can also use a function called
returnthat does the same thing. In GHC versions older than 7.10 and other Haskell implementations,
pure, which can be found in the module
Control.Applicative, is not exported by default, but
- We saw that we can use pure functions like
(a -> b)to change
IO b, using
- We saw that we can chain IO actions using
bind. (Which in Haskell is known as
=<<. Actually, the function
>>=is more commonly used which is just
=<<with the arguments flipped. With
>>=it looks like we are chaining IO actions from left to right)
- We decided that
mainwill be executed by executing the IO actions that compose it.
Now, let's write the program we wanted again using what we have learned,
and let's make the use of
>>= a little bit more explicit by using lambda expressions instead of just currying.
readIntFromUser :: IO Int readIntFromUser = getLine >>= (\line -> pure (read x)) -- which is the same as: getLine >>= pure . read userInt = IO Int userInt = readIntFromUser multiplier :: Int multiplier = 3 mul :: Int -> Int -> Int mul = (*) main :: IO () main = userInt >>= (\input -> pure (mul multiplier input)) >>= (\result -> print result)
Now, let's say we want to first print the user's input and then the result, we could change
main :: IO () main = userInt >>= (\input -> pure (mul multiplier input) >>= (\result -> print input >>= (\_ -> print result)))
But this becomes a little clumsy and not very readable. Fortunately, Haskell has special syntax known as
do notation, let's rewrite
main using it.
main :: IO () main = do input <- userInt result <- pure (mul multiplier input) _ <- print input print result
This looks better. Note that
input <- userInput is analogous to
userInput >>= \input -> ....
Also, We can do even better than that syntax-wise! We can replace
_ <- ... with just
... and we can replace
result <- pure ... with
let result = .... Let's see what this looks like!
main :: IO () main = do input <- userInt let result = mul multiplier input print input print result
Now we can write IO code to be executed, in a concise way, while still retaining the ability to separate evaluation from execution.
There are still more things you can do with IO that are more concise, but with knowing what you know now you have the full power IO and you can do whatever you want with it, including building your own higher order functions to manipulate it.