Fast and concise extensible effects
|Version on this page:||0.3.3.0@rev:1|
|LTS Haskell 20.15:||0.3.3.0@rev:3|
|Stackage Nightly 2023-03-20:||0.3.3.0@rev:3|
|Latest on Hackage:||0.3.3.0@rev:3|
Module documentation for 0.3.3.0
cleff - fast and concise extensible effects
cleff is an extensible effects library for Haskell, with a focus on the balance of performance, expressiveness and ease of use. It provides a set of predefined effects that you can conveniently reuse in your program, as well as low-boilerplate mechanisms for defining and interpreting new domain-specific effects on your own.
cleffdoes not use techniques like Freer monads or monad transformers. Instead,
Effmonad is essentially implemented as a
ReaderT IO. This concrete formulation allows more GHC optimizations to fire, and has lower performance overhead. In microbenchmarks,
The only caveat is that
cleffdoes not support nondeterminism and continuations in the
Effmonad - but after all, most effects libraries has broken nondeterminism support, and we encourage users to wrap another monad transformer with support of nondeterminism (e.g.
ListT) over the main
Effmonad in such cases.
cleffsupports user-defined effects and provides simple yet flexible API for implementing them. Implementations of effects are simply case-splitting functions, and users familiar with
freer-simplewill find it very easy to get along with
cleff. Take a look at the examples.
cleff’s simple underlying structure allows us to implement near-seamless interop with the current ecosystem, mainly classes like
MonadBaseControl. In other words, you can directly use libraries like
cleffwithout writing any “adapter” code.
Traditional effect libraries have many surprising behaviors. For example,
mtlreverts the state when an error is thrown, and has a lot more subtleties when interacting with
Exceptions, so it is able to interact well with
IOand provide semantics that are predictable in the presence of concurrency and exceptions. Moreover, any potentially surprising behavior is carefully documented for each effect.
Higher-order effects are effects that “wraps” monadic computations, like
mask. Implementing higher-order effects is often tedious, or outright not supported in most effect libraries.
polysemyis the first library that aims to provide easy higher-order effects mechanism with its
TacticsAPI. Following its path,
cleffprovides a set of combinators that can be used to implement higher-order effects. These combinators are as expressive as
polysemy’s, and are also easier to use correctly.
Ergonomics without sacrificing flexibility:
cleffdoesn’t have functional dependencies on effects, so you can have e.g. multiple
Stateeffects. As a side effect, GHC will sometimes ask you to provide which effect you’re operating on via
TypeApplications, or otherwise the effect usage will be ambiguous. This can be verbose at times, and we have a solution for that:
cleff-pluginis a GHC plugin that works like
mtl’s functional dependencies, and can resolve most type ambiguities involving effects for you.
This is the code that defines the classic
Teletype effect. It only takes 20 lines to define the effect and two interpretations, one using stdio and another reading from and writing to a list:
import Cleff import Cleff.Input import Cleff.Output import Cleff.State import Data.Maybe (fromMaybe) -- Effect definition data Teletype :: Effect where ReadTTY :: Teletype m String WriteTTY :: String -> Teletype m () makeEffect ''Teletype -- Effect Interpretation via IO runTeletypeIO :: IOE :> es => Eff (Teletype : es) a -> Eff es a runTeletypeIO = interpretIO \case ReadTTY -> getLine WriteTTY s -> putStrLn s -- Effect interpretation via other pure effects runTeletypePure :: [String] -> Eff (Teletype : es) w -> Eff es [String] runTeletypePure tty = fmap (reverse . snd) . runState  . outputToListState . runState tty . inputToListState . reinterpret2 \case ReadTTY -> fromMaybe "" <$> input WriteTTY msg -> output msg -- Using the effect echo :: Teletype :> es => Eff es () echo = do x <- readTTY if null x then pure () else writeTTY x >> echo echoPure :: [String] -> [String] echoPure input = runPure $ runTeletypePure input echo main :: IO () main = runIOE $ runTeletypeIO echo
example/ for more examples.
These are the results of
effectful’s microbenchmarks, compiled by GHC 8.10.7. Each diagram shows the average run time of each effect library’s implementation of an identical program; lower is better. Each benchmark suite has two flavors - shallow and deep - where the shallow variant only uses necessary effects, and the deep variant adds 10 redundant
Reader effects, to simulate more realistic scenarios. Keep in mind that these are very short and synthetic programs, and may or may not tell the accurate performance characteristics of different effect libraries in real use.
If you know about
effectful, you may notice that
effectful seem to make many similar claims and have a similar underlying implementation. In microbenchmarks,
cleff is slightly behind
effectful. This may make you confused about the differences between the two libraries. To put it simply,
cleff has a more versatile and expressive effect interpretation mechanism, and a lighter weight API. In contrast,
effectful gains its performance advantage by providing static dispatch for some internal effects, which means they cannot have multiple interpretations.
These are the useful resources that inspired this library’s design and implementation.
- Extensible Effect: An Alternative to Monad Transformers by Oleg Kiselyov, Amr Sabry, and Cameron Swords.
- Freer Monads, More Extensible Effects by Oleg Kiselyov, and Hiromi Ishii.
effby Alexis King and contributors.
effectfulby Andrzej Rybczak and contributors.
freer-simpleby Alexis King and contributors.
polysemyby Sandy Maguire and contributors.
- Effects for Less by Alexis King.
- Unresolved challenges of scoped effects, and what that means for
effby Alexis King.
- Asynchronous Exception Handling in Haskell by Michael Snoyman.
- Polysemy: Mea Culpa by Sandy Maguire.
- Polysemy Internals: The Effect-Interpreter Effect by Sandy Maguire.
- ReaderT design pattern by Michael Snoyman.
- Safe exception handling by Michael Snoyman.
- Slight performance improvements
runStateLocalthat runs the
Stateeffect with thread-local semantics
- Slight performance improvements
(:>)is now a typeclass by itself instead of a type synonym
makeEffectis now capable of generating sending functions for operations using concrete
Effs for the monad type
- Dependency on
Clarify changelog: new features that are listed “Unreleased” in 0.3.0.0 changelog are in fact released
- [BREAKING] Introduces an
BracketOnError(note that this only affects the effect datatype; there is still
bracketOnErrorfunctions with the same semantics)
mapErrorare slightly (but observably) faster now
- Lifted convenience instances of
MonadZipinstance from the
- [BREAKING] Changed parameter order of
e es esSendto
esSend e es
- [BREAKING] Relaxed fundep of
esSend -> e es(HO combinators may require
Cleff.Internal.*so as not to pollute common namespaces
Trustworthyflags for non-internal modules
sendViafor sending an effect operation along a transformation between effect stacks
raiseNUnderNfor introducing effects under other effects in the effect stack
runWriterBatchas a more efficient
Writerinterpreter that writes
listened values in batch instead of in real time
- Initial API