cleff
Fast and concise extensible effects
https://github.com/re-xyr/cleff#readme
Version on this page: | 0.3.3.0@rev:3 |
LTS Haskell 22.43: | 0.3.3.0@rev:5 |
Stackage Nightly 2023-12-26: | 0.3.3.0@rev:4 |
Latest on Hackage: | 0.3.3.0@rev:5 |
cleff-0.3.3.0@sha256:64e5f3a281dedf82961c04827d2113f88fd7d3f41af6a3cc2341def653bf55b5,6498
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.
In essence, cleff
offers:
-
Performance:
cleff
does not use techniques like Freer monads or monad transformers. Instead,cleff
’sEff
monad is essentially implemented as aReaderT IO
. This concrete formulation allows more GHC optimizations to fire, and has lower performance overhead. In microbenchmarks,cleff
outperformspolysemy
and evenmtl
.The only caveat is that
cleff
does not support nondeterminism and continuations in theEff
monad - 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 mainEff
monad in such cases. -
Low boilerplate:
cleff
supports user-defined effects and provides simple yet flexible API for implementing them. Implementations of effects are simply case-splitting functions, and users familiar withpolysemy
orfreer-simple
will find it very easy to get along withcleff
. Take a look at the examples. -
Interoperability:
cleff
’s simple underlying structure allows us to implement near-seamless interop with the current ecosystem, mainly classes likeMonadUnliftIO
,MonadCatch
andMonadBaseControl
. In other words, you can directly use libraries likeunliftio
,exceptions
andlifted-async
incleff
without writing any “adapter” code. -
Predictable semantics:
Traditional effect libraries have many surprising behaviors. For example,
mtl
reverts the state when an error is thrown, and has a lot more subtleties when interacting withIO
.cleff
implementsState
andWriter
asIORef
operations, andError
asExceptions
, so it is able to interact well withIO
and 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:
Higher-order effects are effects that “wraps” monadic computations, like
local
,catchError
andmask
. Implementing higher-order effects is often tedious, or outright not supported in most effect libraries.polysemy
is the first library that aims to provide easy higher-order effects mechanism with itsTactics
API. Following its path,cleff
provides a set of combinators that can be used to implement higher-order effects. These combinators are as expressive aspolysemy
’s, and are also easier to use correctly. -
Ergonomics without sacrificing flexibility:
Unlike
mtl
,cleff
doesn’t have functional dependencies on effects, so you can have e.g. multipleState
effects. As a side effect, GHC will sometimes ask you to provide which effect you’re operating on viaTypeApplications
, or otherwise the effect usage will be ambiguous. This can be verbose at times, and we have a solution for that:cleff-plugin
is a GHC plugin that works likemtl
’s functional dependencies, and can resolve most type ambiguities involving effects for you.
Example
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
See example/
for more examples.
Benchmarks
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.
countdown
:filesize
:
Differences from effectful
If you know about effectful
, you may notice that cleff
and 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.
References
These are the useful resources that inspired this library’s design and implementation.
Papers:
- 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.
Libraries:
eff
by Alexis King and contributors.effectful
by Andrzej Rybczak and contributors.freer-simple
by Alexis King and contributors.polysemy
by Sandy Maguire and contributors.
Talks:
- Effects for Less by Alexis King.
- Unresolved challenges of scoped effects, and what that means for
eff
by Alexis King.
Blog posts:
- 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.
Changes
Changelog for cleff
0.3.3.0 (2022-05-21)
Changed
- Slight performance improvements
Added
runStateLocal
that runs theState
effect with thread-local semantics
0.3.2.0 (2022-03-13)
Changed
- Slight performance improvements
(:>)
is now a typeclass by itself instead of a type synonym
0.3.1.0 (2022-02-28)
Added
makeEffect
is now capable of generating sending functions for operations using concreteEff
s for the monad type
Removed
- Dependency on
rec-smallarray
0.3.0.1 (2022-02-21)
Clarify changelog: new features that are listed “Unreleased” in 0.3.0.0 changelog are in fact released
0.3.0.0 (2022-02-21)
Changed
- [BREAKING] Introduces an
OnException
primitive forMask
that replacesBracket
andBracketOnError
(note that this only affects the effect datatype; there is stillbracket
andbracketOnError
functions with the same semantics) runError
andmapError
are slightly (but observably) faster now
Added
freshEnumToState
forFresh
onException
andbracketOnError_
forMask
0.2.1.0 (2022-02-13)
Added
- Lifted convenience instances of
Bounded
,Num
,Fractional
,Floating
andIsString
forEff
MonadZip
instance from theMonadComprehensions
extension forEff
runFreshAtomicCounter
forFresh
inputToReader
,mapInput
andbindInput
forInput
mapOutput
andbindOutput
forOutput
runStateIORef
,runStateMVar
andrunStateTVar
forState
0.2.0.0 (2022-02-06)
Changed
- [BREAKING] Changed parameter order of
Handling
class frome es esSend
toesSend e es
- [BREAKING] Relaxed fundep of
Handling
toesSend -> e es
(HO combinators may requireTypeApplication
more often) - Moved
Data.*
modules toCleff.Internal.*
so as not to pollute common namespaces
Added
Trustworthy
flags for non-internal modulessendVia
for sending an effect operation along a transformation between effect stacksraiseUnder
,raiseNUnder
,raiseUnderN
,raiseNUnderN
for introducing effects under other effects in the effect stackrunWriterBatch
as a more efficientWriter
interpreter that writeslisten
ed values in batch instead of in real time
0.1.0.0 (2022-01-31)
- Initial API