I've been meaning to play with the Haskell extensible-effects package, so on a lazy Sunday afternoon I started hacking away porting todaybot. My goal was more to get a hands-on feel for extensible-effects rather than actually replace the existing todaybot implementation.
The existing non-effect-based code is pretty rough-and-ready/script-like. It all runs in the IO monad, and state is threaded round manually in a couple of places.
I started by replacing all the IO actions with calls to the
(Lift IO) effect. The problems I encountered here were:
- type signatures/type checking errors, with initially impenetrable type errors (reminiscent of what happens when you work with Lens). The constraint based style of effect types gives verbose type errors in a form that I was not used to.
- Some interaction that I haven't understood between type signatures/type inference on effects and on lens types and parsec types(!). I needed to add type signatures on top level lens and parser definitions, which I got using typed holes.
- Handling IO exceptions - I was unsure how exceptions would work so for this first cut I ignored exception handling and let the whole bot die if anything goes wrong.
So now I had a bot with a single effect,
Lift IO, with worse error handling than before. I wanted to get this exception handling back in so I wasn't losing functionality. I didn't (and still don't) know of the idiomatic way to handle IO exceptions in (Lift IO).
extensible-effects has exceptions (
Exc) already, and I wanted IO exceptions to be handled using those, with the
Exc IOError effect. I made a wrapper for
lift' which called an IO action and translated IO errors into
Exc IOError exceptions. IO errors then could be handled straightforwardly. Later on it turned out this wasn't enough: code is throwing errors other than IOError which need to be caught (although maybe this is also a bug in the mainline todaybot).
Next, I wanted to start breaking down the use of the IO effect into more focused effects. The one people talk about a lot is logging. There's a
Writer effect in extensible-effects already, and logging by writing a string seemed pretty obvious. There's also a
Trace effect which is pretty similar. Neither had effect handlers that did what I want: translate the logging effect into IO effects (which in turn would be handled by the IO effect handler). This was pretty straightforward to write, though. There was some messing round with type signatures but I got it worked out in the end.
And the last bit I did before I went to bed was put in a couple of Reader effects: one for configuration (which comes from a YAML file), and one for the authentication bearer token, which is requested during runtime. Writing the handlers for these had a similar feel to writing the handler for logging - a few lines of code inserted into boilerplate, messing round with type errors, raising more questions that I might get round to writing about.
Next I would like to implement effects to handle the last two pieces of IO, which are are access to the current time, and HTTP GET/POST calls; and see if I can use a
choice effet instead of
mapM_ to iterate.