effectful
An easy to use, performant extensible effects library.
Version on this page: | 2.3.1.0 |
LTS Haskell 23.1: | 2.5.1.0 |
Stackage Nightly 2024-12-21: | 2.5.1.0 |
Latest on Hackage: | 2.5.1.0 |
effectful-2.3.1.0@sha256:019a7930696aace8bf5767b41c93418fb1398918a9cb5882e7b552187a9f0790,6628
Module documentation for 2.3.1.0
- Effectful
effectful
An easy to use, fast extensible effects library with seamless integration with the existing Haskell ecosystem.
Main features:
-
Very fast (benchmarks).
-
Easy to use API (comparable with usage of the MonadUnliftIO class).
-
Correct semantics in presence of runtime exceptions (no more discarded state updates).
-
Seamless integration with the existing ecosystem (
exceptions
,monad-control
,unliftio-core
,resourcet
etc.). -
Support for thread local and shared state (e.g.
StateT
provides a thread local state, whileMVar
holds a shared state, both approaches have their merits). -
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 asMonadBaseControl
andMonadUnliftIO
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 multipleAlternative
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
andWriterT
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 ofeffectful-core
and additionally provides most features of theunliftio
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:
-
Effects for Less by Alexis King.
-
Monad Transformer State by Michael Snoyman.
Blog posts:
-
ReaderT design pattern by Michael Snoyman.
-
Exceptions Best Practices by Michael Snoyman.
Changes
effectful-2.3.1.0 (2024-06-07)
- Drop support for GHC 8.8.
- Remove inaccurate information from the
Show
instance ofErrorWrapper
. - Add
Effectful.Provider.List
, generalization ofEffectful.Provider
. - Respect
withFrozenCallStack
used by callers ofsend
. - Support exchange of effects between the environment of the handler and the
local one via
localSeqLend
,localLend
,localSeqBorrow
andlocalBorrow
fromEffectful.Dispatch.Dynamic
.
effectful-2.3.0.0 (2023-09-13)
- Deprecate
withConcEffToIO
. - Make
withEffToIO
take an explicit unlifting strategy for the sake of consistency with unlifting functions fromEffectful.Dispatch.Dynamic
and easier to understand API. - Add support for turning an effect handler into an effectful operation via the
Provider
effect. - Add
runErrorWith
andrunErrorNoCallStackWith
toEffectful.Error.Dynamic
andEffectful.Error.Static
. - Add support for having multiple effects of the same type in scope via the
Labeled
effect. - Add various
ByteString
related functions to theFileSystem
effect. - Add the
Console
effect.
effectful-2.2.2.0 (2023-01-11)
- Add
withSeqEffToIO
andwithConcEffToIO
toEffectful
. - Use strict
IORef
andMVar
variants where appropriate. - Make
inject
work with effect stacks sharing a polymorphic suffix.
effectful-2.2.1.0 (2022-11-09)
- Add
localSeqLift
andlocalLift
toEffectful.Dispatch.Dynamic
.
effectful-2.2.0.0 (2022-10-24)
- Change
PrimState
forEff
fromRealWorld
toPrimStateEff
to prevent thePrim
effect from executing arbitraryIO
actions viaioToPrim
. - Deprecate
(:>>)
as GHC can’t efficiently deal with type families. - Add support for the
Alternative
andMonadPlus
instances forEff
via theNonDet
effect.
effectful-2.1.0.0 (2022-08-22)
- Include the
e :> localEs
constraint in theEffectHandler
to allow more flexibility in handling higher order effects. - Do not include internal stack frames in
throwError
fromEffectful.Error.Dynamic
.
effectful-2.0.0.0 (2022-08-12)
- Make storage references in the environment immutable.
- Remove
checkSizeEnv
andforkEnv
fromEffectful.Dispatch.Static.Primitive
. - Add internal versioning of effects to prevent leakage of
unsafeCoerce
. - Make
interpose
andimpose
properly interact with other handlers.
effectful-1.2.0.0 (2022-07-28)
- Change
SuffixOf
toSharedSuffix
and make it behave as advertised. - Add
raiseWith
.
effectful-1.1.0.0 (2022-07-19)
- Don’t reset the
UnliftStrategy
toSeqUnlift
inside the continuation ofwithEffToIO
. - Add
withReader
.
effectful-1.0.0.0 (2022-07-13)
- Initial release.