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.
Different from many previous libraries,
cleff does not use techniques like Freer monads or monad transformers. Instead, the
Eff monad is essentially a
ReaderT IO, which provides predictable semantics and reliable performance. The only caveat is that
cleff does not support nondeterminism and continuations in the
Eff 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 main
Eff monad in such cases.
Eff monad is essentially implemented as a
ReaderT IO. This concrete formulation allows more GHC optimizations to fire, and brings lower performance overhead. This is first done by
eff, and then
effectful; it proved to work, so we followed this path.
polysemy, and is slightly behind
effectful. However, note that
cleff have very different design principles. While
effectful prioritizes performance over anything else (by providing static dispatch),
cleff focuses on balancing expressivity and performance. If you would like minimal performance overhead, consider
cleff supports user-defined effects and provides simple yet flexible API for that. Users familiar with
effectful will find it very easy to get along with
cleff's effect interpretation API include:
- Arbitrary lifting and subsumption of effects
- Interpreting and reinterpreting, without needing to distinguish first-order and higher-order interpreters like
- Translation of effects, i.e. handling an effect in terms of a simple transformation into another effect, as seen in
Traditional effect libraries have many surprising behaviors, such as
mtl reverts state when an error is thrown, and more so when interacting with
IO. By implementing
IORef operations, and
cleff is able to interact well with
IO 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 are effects that take monadic computations. They are often useful in real world applications, as examples of higher-order effect operations include
mask. Implementing higher-order effects is often tedious, or even not supported in some effect libraries.
polysemy is the first library that aims to provide easy higher-order effects mechanism with its
Tactics API. Following its path,
cleff provides 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.
This is the code that defines
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 Data.Maybe (fromMaybe)
-- Effect definition
data Teletype :: Effect where
ReadTTY :: Teletype m String
WriteTTY :: String -> Teletype m ()
-- 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 the effect-zoo microbenchmarks, compiled by GHC 8.10.7. 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:
These are the useful resources that inspired this library's design and implementation.
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.