Musings about FP and CS

A log of my journey through FP and CS

A tale of servant clients

by Clement Delafargue on December 27, 2018

Tagged as: haskell, servant, fretlink.

At Fretlink, we use Servant a lot. All of our haskell webservices are written using servant-server, which is a pure joy to use (see my post on macaroon-based auth).

In addition to servant-server, we also use servant-client, to query the services implemented with servant-server as well as external services (currently, PipeDrive).

While just using servant-server is quite pleasant, raw servant-client use can get complicated.

Let’s consider a small user management API, protected by basic auth:

/
└─ users/
   ├─• GET
   ├─• POST
   └─ :user_id /
      ├─• GET
      ├─• PUT
      └─• DELETE

Transcribed in the servant DSL, we get:

Extracting clients for this API can look like that:

This not very pleasant to read or write: the pattern matching needed to extract endpoints generates quite a lot of syntactic noise, and you need to apply parameters here and there to get access to the inner values.

Fortunately, servant supports generic derivation for clients.

For the API we’ve seen above, we can declare the accompanying records, with some generic magic sprinkled on top of it:

With all that done, you can turn our first client into records, with mkClient. Instead of extracting calls through pattern matching, we can use record accessors.

Well… it’s a bit better than previously, but not way better. listUserClient' is not too bad, but for editUserClient', it’s quite hard to read (and come up with). See how userId and userData are far from withUser and editUser? It only gets worse with bigger routes.

With a little trick, we can fix that:

You can also use a lambda if you’re allergic to sections (replace ($ userId) with (\e -> e userId)). It’s still tedious, but at least it keeps parameters application close to where they’re defined.

Another solution is to use RecordWildCards.

This also prevents the parameters from going too far, but I find it quite verbose. There is little syntactic noise, but I think it obscures the URL structure.

Thankfully, we can still improve on building routes with function application. While right-to-left composition is a great choice in many cases 1, in this context, following the URL more closely seems better. Also, as much as I love abusing sections in the pursuit of η-reduction, ($ userId) is not particularly readable. That being said, using lambdas is a definite no-no, so we’ll add a few helpers.

Since manually passing BasicAuthData everywhere is tedious, we’ll apply Entreprise FP Patterns and use a Reader to handle this. While we’re at it, we can also get a ClientEnv from the reader and actually run the request.

With all this done, and thanks to (>>>) from Control.Category, we can improve readability:

In the codebase I am working on, I went with a >>> / withParam combo, and I am quite happy with the result. Maybe the same could be achieved with lenses, but I have not tried it yet.

This article uses servant clients as an example, but it could have been the same with any library. In the end, we’re composing functions, no more, no less.

I really like the way everything comes together nicely when you have found the right way to combine values. The experience of refactoring haskell code, especially with hlint by my side hasn’t been matched in any other language for now.

Special thanks to @haitlah and @am_i_tom_ for their feedback, to @raveline for pairing with me, and to @ptit_fred for making all of this possible.

A complete code example is available.


  1. Don’t get me started on why everyone uses >>= instead of the clearly superior =<<

comments powered by Disqus