This is the second post in the Haskell for the Impatient series. The required knowledge is:
- Monads
- Haskell data types
- GADTs
I’m going to try a different writing style this time. A kind of Q&A between you and me. Hopefully it’s a bit less dry.
Why am I here?
You are here to learn about algebraic effects!
So why am I handcuffed to a radiator?
In my short experiance as a haskell educator, this seems to have been a necessary pre-requisite to learning about algebraic effects. But don’t worry! As soon as we are done, you can leave
Please be quick
Okay! so ‘Effects’ are a loose way of saying “anything that our program does outside of itself.” So the usual things:
- Printing to the terminal
- Operating a robot arm
- Launching missiles
etc etc. Algebraic effects is a relatively new way of doing these things.
Launching Missiles!?
I don’t recall saying anything about missiles?
Right… Isn’t that exactly what the IO Monad is for?
Yes. And in fact that won’t change. Pretty much everything is indirectly done in the IO Monad
So it’s an indirection over the IO Monad. This seems pointless
That’s the exact kind of attitude that made me have to handcuff you to a radiator! See!
In general, algebraic effects gives us a really nice way to organise code and make programming medium to large projects cleaner and far far easier to reason about. I use them because it’s a pretty good substitute for actually having a software design… It makes everything simpler
Oh okay. So how do we actually use them?
First step is to roughly divide all of the effectful parts of your software into groups, which we will call an effect.
This could be like, everything that handles the GPS chip is in the GPS
effect. Everything that handles the rocket
motor controller is in the Rocket
effect. Everything that handles the payload is in the Payload
effect.
You are talking about missiles again
No I’m not.
Anyway, for each effect, we write interpreters
I thought you said this was simple? You just implied that you are going to write at least 3 different interpreters!
Stop complaining.
We can then access all of the effects in a single monad.
What kind of monad?
The free monad
The free monad? What does that monad do?
Nothing. It is in inherently useless.
This is absurd. Please let me leave
But you see! This is where the interpreters come in. So although the free monad cannot do anything on it’s own, it can still sequence monadic operation. We then use the interpreters to give this monad meaning.
Why is it called the free monad? What does free even mean here?
It means that it’s freely constructed, but I must agree. It’s a silly name. I don’t mind that in functional programming, we take the names of constructions from category theory, but I have to confess, category theorists are really bad at intuitively naming things. I think its more evidence that they hate us.
But okay. I think I’m following. The free monad is used to sequence effects and the interpreters give them meaning
Yes! Want to see an example?
Not really
So I’ll be using the freer-simple
library because I think it’s the easiest to understand.
Let’s start by defining a logging effect. We are essentially defining a domain specific language for logging.
We have to use GADTs where r
is the return type of each effect.
--| Projectile Status
data Status = ...
data Log r where
LogMsg :: String -> Log ()
ReportStatus :: Status -> Log ()
Projectile Status? Wait. You are programming missiles!?
What is it with your obsession with missiles? I’m a bit worried about your mental health.
So it’s a very simple language for logging. The next step is to explain how to use it! Normally, we would write:
makeEffect 'Log
which, in our case will use template Haskell (you will need this enabled) to generate two functions for us. But for didactic purposes, I will write these functions by hand:
logMsg :: Member Log es => String -> Eff es ()
logMsg = send . LogMsg
reportStatus :: Member Log es => Status -> Eff es ()
reportStatus = send . ReportStatus
Okay. I’m no longer following. A lot is going on here
So Eff es a
is our free monad. es
is actually a list of types! So, to use my example from before, you could have
Eff [GPS, Rocket, Payload] a
. The types in this list are effects.
The constraint, Member Log es
is simply saying that, in the type level list, es
, there must be an element Log
, but we
don’t care where.
The way I like to think of send is that it sends an effect to the correct effect interpreter. Speaking of which, let’s write an interpreter for our logging effect
runLogger :: (LastMember IO es) => Eff (Log : es) a -> Eff es a
runLogger = interpretM go
where go :: Log r -> IO r
go (LogMsg msg) = putStrLn msg
go (ReportStatus status) = print status
Again, explain
So look at the type, it takes in a free monad with a list of effects with the head of that list being log, and it
removes the logging effect by interpreting it. If we were to use interpret
, we would interpret Log
in terms
of the other effects in es
. But because we don’t use them, we can use interpretM
to interpret the effects in the
bottom effect. In this case, because of the constraint (LastMember), we know the last effect is the IO
effect and
so we write a little interpreter for the IO monad.
To use the first example, we run all the effects like this:
runEverything :: Eff [GPS, Rocket, Payload, IO] a -> IO a
runEverything = runM . runPayload . runRocket . runGPS
Okay. This just looks like an overcomplicated way of using the IO monad. What have I gained
So firstly, to take the log example, maybe a customer comes back to us and wants us to time stamp the logs. Now all we have to do is change the effect interpreter and we don’t have to touch any of the rest of our code.
Also, maybe it’s a command line program and we have a flag for logging to a file instead? Then we write two interpreters, one to log to standard output, the other to log to a file. We can change which interpreter we use based on the command line flag.
Maybe we have a database effect that connects to an SQL database over TCP. Bit annoying for running tests. But we could interpret that as a state monad when we run our tests.
The key thing is that, by separating the language of our effects from their implementation, we can interpret them as we like, and differently depending on the context.
Does that make sense?
That actually is quite cool! It reminds me of dependency injection!
Eh. Sure. But unlike dependency injection, it actually makes sense.
If you want a simple example, I have wrote an example application for my students here. The server uses effects, not the client.
Can you un-cuff me?
But we are having so much fun?
…
Fine