cleff: Fast and concise extensible effects

[ bsd3, control, effect, language, library ] [ Propose Tags ]

Please see the README on GitHub at https://github.com/re-xyr/cleff#readme


[Skip to Readme]

Flags

Manual Flags

NameDescriptionDefault
dynamic-ioe

Make IOE a real effect. This is only for reference purposes and should not be enabled in production code.

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.1.0.0, 0.2.0.0, 0.2.1.0, 0.3.0.0, 0.3.0.1, 0.3.1.0, 0.3.2.0, 0.3.3.0 (info)
Change log CHANGELOG.md
Dependencies atomic-primops (>=0.8 && <0.9), base (>=4.12 && <5), containers (>=0.6 && <0.7), exceptions (>=0.10 && <0.11), microlens (>=0.4.9 && <0.5), monad-control (>=1 && <1.1), primitive (>=0.6 && <0.8), template-haskell (>=2.14 && <3), th-abstraction (>=0.2.11 && <0.5), transformers (>=0.5 && <0.7), transformers-base (>=0.4.5 && <0.5), unliftio (>=0.2.8 && <0.3) [details]
License BSD-3-Clause
Copyright 2021 Xy Ren
Author Xy Ren
Maintainer xy.r@outlook.com
Category Control, Effect, Language
Home page https://github.com/re-xyr/cleff#readme
Bug tracker https://github.com/re-xyr/cleff/issues
Source repo head: git clone https://github.com/re-xyr/cleff
Uploaded by daylily at 2022-01-31T04:55:52Z
Distributions LTSHaskell:0.3.3.0
Reverse Dependencies 1 direct, 0 indirect [details]
Downloads 921 total (30 in the last 30 days)
Rating 1.75 (votes: 2) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2022-01-31 [all 1 reports]

Readme for cleff-0.1.0.0

[back to package description]

cleff - fast and concise extensible effects

GitHub Workflow Status Hackage

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.

Overview

Different from many previous libraries, cleff does not use techniques like Freer monads or monad transformers. Instead, the Eff monad is esentially 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.

Performance

cleff's Eff monad is esentially 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.

In microbenchmarks, cleff outperforms polysemy, and is slightly behind effectful. However, note that effectful and 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 effectful.

Low-boilerplate

cleff supports user-defined effects and provides simple yet flexible API for that. Users familiar with polysemy, freer-simple or effectful will find it very easy to get along with cleff. 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 polysemy
  • Translation of effects, i.e. handling an effect in terms of a simple transformation into another effect, as seen in polysemy's rewrite and freer-simple's translate

Predictable semantics

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 State and Writer as IORef operations, and Error as Exceptions, 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

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 local, catchError and 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 mechanicsm 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.

Example

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 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 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: big-stack benchmark result
  • countdown: countdown benchmark result
  • file-sizes: file-sizes benchmark result
  • reinterpretation: reinterpretation benchmark result

References

These are the useful resourses that inspired this library's design and implementation.

Papers:

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: