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.44: | 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,6498Module 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:
cleffdoes not use techniques like Freer monads or monad transformers. Instead,cleff’sEffmonad is essentially implemented as aReaderT IO. This concrete formulation allows more GHC optimizations to fire, and has lower performance overhead. In microbenchmarks,cleffoutperformspolysemyand evenmtl.The only caveat is that
cleffdoes not support nondeterminism and continuations in theEffmonad - 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 mainEffmonad in such cases. -
Low boilerplate:
cleffsupports user-defined effects and provides simple yet flexible API for implementing them. Implementations of effects are simply case-splitting functions, and users familiar withpolysemyorfreer-simplewill 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,MonadCatchandMonadBaseControl. In other words, you can directly use libraries likeunliftio,exceptionsandlifted-asyncincleffwithout writing any “adapter” code. -
Predictable semantics:
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 withIO.cleffimplementsStateandWriterasIORefoperations, andErrorasExceptions, so it is able to interact well withIOand 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,catchErrorandmask. 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 itsTacticsAPI. Following its path,cleffprovides 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,cleffdoesn’t have functional dependencies on effects, so you can have e.g. multipleStateeffects. 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-pluginis 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:
effby Alexis King and contributors.effectfulby Andrzej Rybczak and contributors.freer-simpleby Alexis King and contributors.polysemyby Sandy Maguire and contributors.
Talks:
- Effects for Less by Alexis King.
- Unresolved challenges of scoped effects, and what that means for
effby 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
runStateLocalthat runs theStateeffect 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
makeEffectis now capable of generating sending functions for operations using concreteEffs 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
OnExceptionprimitive forMaskthat replacesBracketandBracketOnError(note that this only affects the effect datatype; there is stillbracketandbracketOnErrorfunctions with the same semantics) runErrorandmapErrorare slightly (but observably) faster now
Added
freshEnumToStateforFreshonExceptionandbracketOnError_forMask
0.2.1.0 (2022-02-13)
Added
- Lifted convenience instances of
Bounded,Num,Fractional,FloatingandIsStringforEff MonadZipinstance from theMonadComprehensionsextension forEffrunFreshAtomicCounterforFreshinputToReader,mapInputandbindInputforInputmapOutputandbindOutputforOutputrunStateIORef,runStateMVarandrunStateTVarforState
0.2.0.0 (2022-02-06)
Changed
- [BREAKING] Changed parameter order of
Handlingclass frome es esSendtoesSend e es - [BREAKING] Relaxed fundep of
HandlingtoesSend -> e es(HO combinators may requireTypeApplicationmore often) - Moved
Data.*modules toCleff.Internal.*so as not to pollute common namespaces
Added
Trustworthyflags for non-internal modulessendViafor sending an effect operation along a transformation between effect stacksraiseUnder,raiseNUnder,raiseUnderN,raiseNUnderNfor introducing effects under other effects in the effect stackrunWriterBatchas a more efficientWriterinterpreter that writeslistened values in batch instead of in real time
0.1.0.0 (2022-01-31)
- Initial API