Servant style handlers for Yesod
by @pseudonom on December 21, 2015
Version 0.2 of Servant (the oldest version recognizable as modern Servant) was released at the end of 2014. By that point, Front Row was already pretty firmly commited to Yesod. But using Yesod doesn’t mean we can’t learn from Servant.
Servant!
One feature of Servant that I find compelling is the way that it constructs its return types. That is, by making handlers with types like Handler (Int, Int)
we have both good visibility and composability.
A fuller example is:
Our problem
Compare this to the incremental version in Yesod:
Gross. First, we can’t get a good sense of what the handlers do by just looking at their types. What does that Value
represent? Turns out, it’s something different in each handler. Second, we’ve introduced partiality because, by converting our [User]
to a Value
, we’ve lost information and headed toward loosey-goosey dynamic typing.
Surely, we’d never write code this ugly. Instead, we might end up with something like:
which is safer but entails some boilerplate and requires us to modify routes that we’ve already written when composing them with new routes.
A solution
It turns out that we can pretty easily fix the Yesod version to recover the compositionality and transparency of the Servant version:
Those GHC extensions may look a little scary, but they turn out to be pretty mundane. UndecidableInstances
is required only because GHC can’t tell in advance that trying to resolve our ToContent
instance won’t send it into an infinite loop. Once GHC successfully resolves the instance (and it will, unless you add some very strange instances), we can be confident that we haven’t given up any type safety. And the OVERLAPPABLE
ibility of the instances is pretty trouble-free in practice, in part because there are fairly few instances of ToContent
.
Front Row has been using this approach in production for several weeks now. Overall, it’s been a pretty nice improvement from just 5 lines of code.