Skip to content

Instantly share code, notes, and snippets.

@DarinM223
Last active January 8, 2019 12:10
Show Gist options
  • Save DarinM223/3458a555e828b4167ebc23f6427dc951 to your computer and use it in GitHub Desktop.
Save DarinM223/3458a555e828b4167ebc23f6427dc951 to your computer and use it in GitHub Desktop.
Effectful Haskell tips

Tips I found useful for writing effectful Haskell

Here are some tips I found useful for writing Haskell code that abstracts over effects, along with the articles that describe them.

  1. Main transformer should be a newtype around ReaderT Config IO, where Config is a record with all the mutable references to everything needed for the application.

  2. Add "Has***" typeclasses to the fields in Config so that functions can use typeclass constraints like (MonadReader c r, HasFoo c, HasBar c) to specify only what they want so that the functions aren't tied to the config type. To reduce boilerplate, generic-lens can be used to automatically generate HasType typeclass instances for record fields.

https://www.fpcomplete.com/blog/2017/06/readert-design-pattern

  1. Separate business logic from implementation details with records with effectful functions in it and explicitly pass them into functions. MTL-style typeclasses are harder to work with, since you need to create a newtype and deal with either deriving via boilerplate or n^2 instances boilerplate. It also can't swap effectful behavior easily and since typeclasses are worked with at the type level it results in having to work with lots of extensions like functional dependencies and data kinds, making it harder for beginners. If it is necessary to pass around large amounts of records into many functions, its possible to create a record containing the records and use Has*** typeclass constraints to let you take out the records you need.

  2. For error handling there are two options: m (Either e a) and (MonadThrow m) => m a. Returning Either is preferred if errors are common and expected and MonadThrow is better for more uncommon conditions.

  3. If you are working with IO code where exhaustively handling every error branch with pattern matching is important you can convert between IO and ExceptT SomeException UIO, where UIO is IO without exceptions.

  4. When testing the code, you might be able to write a mock transformer that doesn't use IO at all (like StateT ExceptT ...) and pass in records with effectful functions that work with the different transformer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment