tldr: I can write Haskell that works, but often think I could structure it better. I’d like some feedback and/or suggestions on how to improve some code.
Background
I’m at the stage with Haskell where I find I can generally write code that does what I want it to, but often find that it ends up bulky or difficult to test, or I find myself repeating more than I’d like to.
Although I have plenty of experience working in other languages, my knowledge of best practices in Haskell is lacking and I feel I might be missing some “jumps” of the form “If I’m writing this over and over I should consider refactoring to that”.
I’m currently working on a backend server for a website I manage which involves a concept of booking tickets for events. I’m using servant
for the API structuring and polysemy
for effect management.
The Problem
I find I end up writing server endpoints in a form like:
endpoint :: X -> Y -> Z -> Sem r a
endpoint x y z = do
doSomething
x
y
>>= \case
ASuccessValue -> do
logSomething
doSomethingElse >>= \case
ADifferentSuccessValue -> do
... etc
ADifferentFailureValue err ->
failureResponse $ "some message: " <> err
AFailureValue err ->
failureResponse err
This typically results in long chains of x >>= \case { ... }
which can become quite hard to read, particularly when the actions within them are functions taking arguments spanning several lines.
My Thoughts on Approaches
I think a major part of it might be that I’m using sum types for a lot of pass/fail kind of cases, such as SendEmailSuccess | SendEmailFailure
rather than Either
. The reason for this is partly that if I were to refactor to use something like Either
, I’m not sure how I could retain some of the injected debugging information for failures such as "some message"
in the example above.
Another approach I’ve considered to build on that would be to use a custom monad with failure like Either
, but map values such as SendEmailFailure
to error values in that monad.
Request
I’d really appreciate feedback from anyone in the community who could point me in the direction of any patterns for this kind of problem which work in Haskell or what works for them, or if you have any comments on my ideas for approaches.