An easy to use, performant extensible effects library with seamless integration with the existing Haskell ecosystem.
Go to file
2021-07-01 11:00:01 +02:00
.github/workflows Drop support for GHC 8.6 2021-06-26 18:54:13 +02:00
bench Add ST reference to the countdown benchmark 2021-06-28 22:03:03 +02:00
examples Don't require Error types to have an Exception instance 2021-06-24 11:59:56 +02:00
src/Effectful Add the Fail effect 2021-06-30 20:56:50 +02:00
tests Update description of Env 2021-06-27 00:25:49 +02:00
.gitignore .gitignore fix for Stack configurations (#12) 2021-07-01 11:00:01 +02:00
cabal.haskell-ci Initial commit 2021-06-11 14:17:19 +02:00
CHANGELOG.md Change name to effectful 2021-06-13 20:08:33 +02:00
effectful.cabal Add the Fail effect 2021-06-30 20:56:50 +02:00
LICENSE Initial commit 2021-06-11 14:17:19 +02:00
README.md Add comparison with eff 2021-06-23 02:52:54 +02:00

effectful

Build Status Hackage

Note: this is a pre-release.

An easy to use, performant extensible effects library with seamless integration with the existing Haskell ecosystem.

Main features:

  1. Very fast (benchmark results with GHC 8.8.4: countdown, filesize, comparison with eff).

  2. Easy to use API (no boilerplate code and dealing with arcane types).

  3. Correct semantics in presence of runtime exceptions (no more lost or discarded state).

  4. Seamless integration with the existing ecosystem (exceptions, monad-control, unliftio-core, resourcet etc.).

  5. Effects can be defined for either

    • static dispatch (as fast as it gets, single interpretation) or

    • dynamic dispatch (a bit slower, multiple interpretations),

    depending on your needs.

Motivation

Do we really need yet another library for handling effects? There's freer-simple, fused-effects, polysemy, eff and probably a few more.

Unfortunately, of all of them only eff is a promising proposition because of reasonable performance characteristics (see the talk "Effects for Less" linked below for more information) and potential for good interoperability with the existing ecosystem.

The second point is arguably the most important, because it allows focusing on things that matter instead of reinventing all kinds of wheels and is crucial for adoption of the library.

However, eff uses delimited continuations underneath, which:

  • Are not yet supported by GHC (though the proposal for including support for them has been accepted).

  • Are quite hard to understand.

  • Make the library "too powerful" in a sense as it faces a few issues with no clear path towards their resolution.

On the other hand, if support for continuations is excluded from the handler monad, then the ability to define effects with non-linear control flow (such as NonDet) is lost. Arguably it's a small price to pay for predictability, because such specialized effects are needed rarely and locally, at which point a dedicated, well established solution such as conduit, list-t or logict can be used.

This is where effectful comes in. The Eff monad it uses is essentially a ReaderT over IO on steroids, allowing us to dynamically extend its environment with data types that represent effects or their interpreters.

Because this concept is so simple:

  • It's reasonably easy to understand what is going on under the hood.

  • The Eff monad being a reader allows for seamless interoperability with ubiquitous classes such as MonadBaseControl and MonadUnliftIO as well as support for handling runtime exceptions without worrying about lost or discarded state (see the talk "Monad Transformer State" linked below for more information).

What is more:

  • The Eff monad is concrete, so GHC has many possibilities for optimization, which results in a very fast code at a default optimization level. There is no need to mark every function INLINE or enable additional optimization passes, it just works.

  • If an advanced effect with non-linear control flow is needed, you can always stick a transformer that implements it on top of Eff in a local context.

In conclusion, effectful aims to reduce duplication and bring back performance to "boring" transformer stacks, most of which are a dozen of newtype'd StateT or ReaderT transformers, each with a few associated operations (usually tied to a type class), not to replace monad transformers altogether.

Example

A Filesystem effect with two handlers, one that runs in IO and another that uses an in-memory virtual file system can be found here.

Resources

Resources that inspired the rise of this library and had a lot of impact on its design.

Talks:

Blog posts: