Skip to main content

fused-effects, Tagless-Final, HMock

My website software does a lot of IO. In particular, the “filesystem builder” component is a static site generator that reads content and configuration from a source directory and writes output to a build directory, so it inherently performs IO throughout the business logic. In early versions, I ran into situations where I really wanted to have tests for components that involve IO. Haskell supports many different ways to “architect” such code, and FeedPipe gave me a good chance to do some experiments in a project that is similar yet less complex.

fused-effects

I decided to use the fused-effects library when I started work on FeedPipe. It is one of several extensible effects libraries that has been pretty popular lately. When using such libraries, the API (specification/syntax) is completely separate from the implementation (interpretation/semantics). In FeedPipe, such functionality allows for separate implementations for running the software and for testing it.

Implementation went very smoothly, and I quite like the library! I have experimented with it a few times in the past, and it has greatly improved. In particular, effects are now written using GADTs, which makes development of effects and carriers quite easy. I also really like that the only dependencies are base and transformers! I have not tested it yet, but it should be compatible with a wide range of GHC versions!

There is one thing that I do not yet know how to do when using the library: concurrency. Carriers are generally implemented using monad transformers, and effects are composed by stacking newtype wrappers as is commonly done with mtl. Perhaps concurrency can be used by “simply” adding a MonadUnliftIO (or MonadBaseControl) constraint on the monad in the type signature of a function and lifting the concurrency operation, but I have yet to try it. I would like to experiment with it, but now is not an appropriate time to do so.

Note that I generally avoid stacking transformers because it results in poor performance. The fused-effects README states that “performance is excellent: approximately on par with mtl.” This brings up an important point: performance is relative. Due to past problems, I avoid stacking transformers in performance-sensitive applications such as servers, but mtl performance is indeed acceptable in applications where high performance is not required. I think that FeedPipe is such an application where using mtl (or fused-effects) is fine.

When using mtl, the performance problem can be avoided by not stacking transformers too deeply. For example, the ReaderT design pattern uses a single monad transformer over IO, and mutable references are used to manage any mutable state. To implement an API that uses IO, instances can be defined for that monad using lift, without having to add an additional newtype wrapper. With fused-effects, however, a newtype wrapper is added even in these cases. See Control.Carrier.Trace.Printing for a simple example.

Tagless-Final

When I decided to rewrite FeedPipe using a design that is more like my website software (see the FeedPipe Design blog entry), I switched to a tagless-final style for a number of reasons:

  • It is what I use in the website software.
  • As with effects libraries, the API is completely separate from the implementation, allowing me to elegantly test components that do IO.
  • I am excited about a new library that I can use for testing. (See below.)
  • I do not need to worry about the performance implications of stacking monad wrappers too deeply.
  • I know how to make use of concurrency.

HMock

Since one of my goals is to test components that involve IO, I started development of a mocking library that would work well with my website software, inspired by Alexis King’s monad-mock. I have not had much time to put toward it, however, so I was really happy when Chris Smith announced the release of HMock earlier this year!

HMock currently only supports mocking of APIs defined using monadic type classes, but Chris plans on adding support for other styles of effectful code, including fused-effects! The features are very impressive. If you would like a quick overview, check out the presentation that Chris gave at the Haskell Implementors’ Workshop forum of ICFP 2021.

Note that since the library is only used when testing, I do not worry about the added dependencies. The library supports GHC versions 8.4 through 9.0, however, so I cannot use it to test projects using GHC 8.2 (released July 22, 2017). I generally aim to support five years of releases, but perhaps this library provides sufficient benefits to drop support for GHC 8.2 early…

Author

Travis Cardwell

Published

Tags
Related Blog Entries