effectful

Build Status Hackage Dependencies Stackage LTS Stackage Nightly

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

Main features:

  1. Very fast (benchmarks).

  2. Easy to use API (comparable with usage of the MonadUnliftIO class).

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

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

  5. Support for thread local and shared state (e.g. StateT provides a thread local state, while MVar holds a shared state, both approaches have their merits).

  6. Support for statically (implementation determined at compile time) and dynamically (implementation determined at run time) dispatched effects.

Motivation

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

It needs to be noted that of all of them only the work-in-progress eff library is a promising proposition because of reasonable performance characteristics (see the talk Effects for Less 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, hence being a necessary condition for broader adoption of the library.

Unfortunately, the development of eff has stalled due to a few subtle issues related to its use of delimited continuations underneath.

What about mtl?

It’s true that its “effects as classes” approach is widely known and used often.

However:

  • mtl style effects are slow.

  • The majority of popular monad transformers (except ReaderT) used for effect implementations are rife with subtle issues.

These are problematic enough that the ReaderT design pattern was invented. Its fundamentals are solid, but it’s not an effect system.

A solution? Use the ReaderT pattern as a base and build around it to make an extensible effects library! This is where effectful comes in. The Eff monad it uses is essentially a ReaderT over IO on steroids, allowing us to extend its environment with data types representing effects.

This concept is quite simple, so:

  • 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 and solves issues of monad transformers mentioned above.

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 explicitly mark functions with INLINE pragmas or enable additional optimization passes, it just works.

Any downsides?

As always, there’s no free lunch. The Eff monad doesn’t support effect handlers that require the ability to suspend or capture the rest of the computation and resume it later (potentially multiple times). This prevents effectful from providing (in particular):

  • A NonDet effect handler that executes multiple Alternative branches and collects their results.

  • A Coroutine effect.

It needs to be noted however that such NonDet effect handler in existing libraries is broken and none of the ones with support for higher order effects provide the Coroutine effect, so arguably it’s not a big loss.

If you need such capability in your application, there are well established libraries such as conduit or list-t that can be used with effectful without any hassle.

Summary

effectful is an extensible effects library that aims to be the replacement for:

  • The bare ReaderT pattern by being essentially its enriched version.

  • Monad transformer stacks typically encountered in the wild (i.e. consisting of a dozen of newtype’d ExceptT, ReaderT, StateT and WriterT transformers and their derivatives) by providing equivalent effects with improved semantics, performance, usability and making it easy to reuse them for your own effects.

It doesn’t try to make monad transformers obsolete, so you’re free to use it with ConduitT, ContT, ListT etc. when necessary.

Package structure

The library is split among several packages:

  • The effectful-core package contains the core of the library along with basic effects. It aims for a small dependency footprint and provides building blocks for more advanced effects.

  • The effectful-plugin package provides an optional GHC plugin for improving disambiguation of effects (see here for more information).

  • The effectful-th package provides utilities for generating bits of effect-related boilerplate via Template Haskell.

  • The effectful package re-exports public modules of effectful-core and additionally provides most features of the unliftio package divided into appropriate effects.

Examples

For the examples see the Introduction sections of Effectful.Dispatch.Dynamic and Effectful.Dispatch.Static (when in doubt, start with dynamic dispatch).

Acknowledgements

To all contributors of existing effect libraries - thank you for putting the time and effort to explore the space. In particular, conversations in issue trackers of cleff, eff, freer-simple, fused-effects and polysemy repositories were invaluable in helping me discover and understand challenges in the space.

Resources

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

Talks:

Blog posts:


Changes

effectful-core-2.5.0.0 (2024-10-23)

  • Add plusEff (specialized version of <|>) to Effectful.NonDet and make emptyEff and sumEff generate better call stacks.
  • Explicitly define setByteArray# and setOffAddr# in the Prim instance of Ref for primitive < 0.9.0.0.
  • Bugfixes:
    • OnEmptyRollback strategy of the NonDet effect is no longer broken.
  • Breaking changes:
    • Remove restoreEnv function from Effectful.Dispatch.Static.Primitive since it was broken.
    • Base Effectful.Exception on Control.Exception instead of the safe-exceptions library for consistency with provided MonadThrow and MonadCatch instances.

effectful-core-2.4.0.0 (2024-10-08)

  • Add utility functions for handling effects that take the effect handler as the last parameter to Effectful.Dispatch.Dynamic.
  • Add utility functions for handling first order effects to Effectful.Dispatch.Dynamic.
  • Improve Effectful.Labeled, add Effectful.Labeled.Error, Effectful.Labeled.Reader, Effectful.Labeled.State and Effectful.Labeled.Writer.
  • Add throwErrorWith and throwError_ to Effectful.Error.Static and Effectful.Error.Dynamic.
  • Add HasCallStack constraints where appropriate for better debugging experience.
  • Add a SeqForkUnlift strategy to support running unlifting functions outside of the scope of effects they capture.
  • Add Effectful.Exception with appropriate re-exports from the safe-exceptions library.
  • Bugfixes:
    • Ensure that a LocalEnv is only used in a thread it belongs to.
    • Properly roll back changes made to the environment when OnEmptyRollback policy for the NonDet effect is selected.
    • Fix a bug in stateM and modifyM of thread local State effect that might’ve caused dropped state updates (#237).
  • Breaking changes:
    • localSeqLend, localLend, localSeqBorrow and localBorrow now take a list of effects instead of a single one.
    • Effectful.Error.Static.throwError now requires the error type to have a Show constraint. If this is not the case for some of your error types, use throwError_ for them.
    • ThrowError operation from the dynamic version of the Error effect was replaced with ThrowErrorWith.
    • stateEnv and modifyEnv now take pure modification functions. If you rely on their old forms, switch to a combination of getEnv and putEnv.
    • runStateMVar, evalStateMVar and execStateMVar now take a strict MVar' from the strict-mutable-base package.

effectful-core-2.3.1.0 (2024-06-07)

  • Drop support for GHC 8.8.
  • Remove inaccurate information from the Show instance of ErrorWrapper.
  • Add Effectful.Provider.List, generalization of Effectful.Provider.
  • Respect withFrozenCallStack used by callers of send.
  • Support exchange of effects between the environment of the handler and the local one via localSeqLend, localLend, localSeqBorrow and localBorrow from Effectful.Dispatch.Dynamic.

effectful-core-2.3.0.1 (2023-11-13)

  • Prevent internal functions from appending call stack frames to handlers.

effectful-core-2.3.0.0 (2023-09-13)

  • Deprecate withConcEffToIO.
  • Make withEffToIO take an explicit unlifting strategy for the sake of consistency with unlifting functions from Effectful.Dispatch.Dynamic and easier to understand API.
  • Add support for turning an effect handler into an effectful operation via the Provider effect.
  • Add runErrorWith and runErrorNoCallStackWith to Effectful.Error.Dynamic and Effectful.Error.Static.
  • Add support for having multiple effects of the same type in scope via the Labeled effect.

effectful-core-2.2.2.2 (2023-03-13)

  • Allow inject to turn a monomorphic effect stack into a polymorphic one.
  • Use C sources only with GHC < 9.
  • Force inlining of bracket early to work around excessive inlining problem with GHC 9.6 (https://gitlab.haskell.org/ghc/ghc/-/issues/22824).

effectful-core-2.2.2.1 (2023-01-12)

  • Stop using the internal library because of bugs in stack.

effectful-core-2.2.2.0 (2023-01-11)

  • Add withSeqEffToIO and withConcEffToIO to Effectful.
  • Use strict IORef and MVar variants where appropriate.
  • Make inject work with effect stacks sharing a polymorphic suffix.

effectful-core-2.2.1.0 (2022-11-09)

  • Add localSeqLift and localLift to Effectful.Dispatch.Dynamic.

effectful-core-2.2.0.0 (2022-10-24)

  • Change PrimState for Eff from RealWorld to PrimStateEff to prevent the Prim effect from executing arbitrary IO actions via ioToPrim.
  • Deprecate (:>>) as GHC can’t efficiently deal with type families.
  • Add support for the Alternative and MonadPlus instances for Eff via the NonDet effect.

effectful-core-2.1.0.0 (2022-08-22)

  • Include the e :> localEs constraint in the EffectHandler to allow more flexibility in handling higher order effects.
  • Do not include internal stack frames in throwError from Effectful.Error.Dynamic.

effectful-core-2.0.0.0 (2022-08-12)

  • Make storage references in the environment immutable.
  • Remove checkSizeEnv and forkEnv from Effectful.Dispatch.Static.Primitive.
  • Add internal versioning of effects to prevent leakage of unsafeCoerce.
  • Make interpose and impose properly interact with other handlers.

effectful-core-1.2.0.0 (2022-07-28)

  • Change SuffixOf to SharedSuffix and make it behave as advertised.
  • Add raiseWith.

effectful-core-1.1.0.0 (2022-07-19)

  • Don’t reset the UnliftStrategy to SeqUnlift inside the continuation of withEffToIO.
  • Add withReader.

effectful-core-1.0.0.0 (2022-07-13)

  • Initial release.