cleff
Note: this library is still WIP! At the same time, you can preview it at the package candidate.
cleff is an extensible effects library for Haskell. It provides a set of predefined effects that you can conveniently reuse in your program, as well as mechanisms for defining and interpreting new domain-specific effects on your own.
cleff supports higher-order effects, like bracket, local and mask, and provides a set of easy-to-use combinators to interpret them.
cleff is fast. It is essentially built on top of a ReaderT IO, which admits many compiler optimizations.
cleff requires little boilerplate to interpret an effect; the interface is similar to that of freer-simple and polysemy, but sometimes even simpler.
cleff takes inspiration from freer-simple, polysemy and effectful. If you have used any of them, it would be very easy to get along with cleff.
Rationale
We have a bunch of effect libraries out there, why another? To put it simply: cleff is an attempt of implementing an expressive effect system, with good ergonomics and a unified API, and without sacrificing much performance.
In particular, cleff uses a ReaderT IO as the underlying representation of the Eff monad. With this representation, more optimizations are possible, and thus brings hope for lower performance overhead. The effectful library already uses the approach, and proved it to be true; so we follow this path. Indeed, this means that we lose nondeterminism and continuations in the Eff monad - but after all, most effects libraries has broken nondeterminism support, and you could always wrap another monad transformer with support of nondeterminism (e.g. ListT) over the main Eff monad.
However, cleff is also like polysemy, in the sense that it supports very flexible and user-friendly effect interpretation. This includes support for arbitrary effect lifting and subsumption, as well as interpreting higher-order effects, with arguably even less boilerplate than polysemy.
In terms of performance, cleff outperforms polysemy in microbenchmarks, and is slightly behind effectful. However, note that effectful and cleff have very different design principles. While effectful prioritizes performance (by providing static dispatch), cleff focuses on allowing more expressive higher-order effect interpretation and providing user-friendly interpretation combinators. If you would like minimal performance overhead, please still consider effectful.
In conclusion, cleff is an effect library that tries to find a good balance between simplicity, performance, and expressivity.
Example
The classical Teletype effect:
import Cleff
import Cleff.Input
import Cleff.Output
import Cleff.State
import Data.Maybe (fromMaybe)
data Teletype :: Effect where
ReadTTY :: Teletype m String
WriteTTY :: String -> Teletype m ()
makeEffect ''Teletype
runTeletypeIO :: IOE :> es => Eff (Teletype ': es) a -> Eff es a
runTeletypeIO = interpretIO \case
ReadTTY -> getLine
WriteTTY s -> putStrLn s
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
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 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:
big-stack:
countdown:
file-sizes:
reinterpretation:
References
These are the useful resourses that inspired this library.
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:
Blog posts: