Skip to content

Commit

Permalink
Rewrite README
Browse files Browse the repository at this point in the history
  • Loading branch information
re-xyr committed Jan 31, 2022
1 parent 9d50253 commit ce68248
Showing 1 changed file with 40 additions and 17 deletions.
57 changes: 40 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
# cleff
# `cleff` - fast and concise extensible effects

> Note: this library is still WIP! At the same time, you can preview it [at the package candidate](https://hackage.haskell.org/package/cleff-0.1.0.0/candidate).
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/re-xyr/cleff/build)](https://github.com/re-xyr/cleff/actions/workflows/build.yaml)
[![Hackage](https://img.shields.io/hackage/v/cleff)](https://hackage.haskell.org/package/cleff)

`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` 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.

- `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`.
## Overview

## Rationale
Different from [many](`polysemy`) [previous](`fused-effects`) [libraries](`freer-simple`), `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](https://github.com/polysemy-research/polysemy/issues/246), and we encourage users to wrap another monad transformer with support of nondeterminism (e.g. `ListT`) over the main `Eff` monad in such cases.

We have [a][`polysemy`] [bunch][`fused-effects`] [of][`effectful`] [effect][`eff`] [libraries][`freer-simple`] 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.
### Performance

In particular, `cleff` uses a `ReaderT IO` as the underlying representation of the `Eff` monad. With this representation, [more optimizations are possible][alexis-talk], 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](https://github.com/polysemy-research/polysemy/issues/246), and you could always wrap another monad transformer with support of nondeterminism (e.g. `ListT`) over the main `Eff` monad.
`cleff`'s `Eff` monad is esentially implemented as a `ReaderT IO`. This concrete formulation [allows more GHC optimizations to fire][alexis-talk], and brings lower performance overhead. This is first done by [`eff`], and then [`effectful`]; it proved to work, so we followed this path.

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](https://hackage.haskell.org/package/cleff-0.1.0.0/candidate/docs/Cleff.html#g:4), as well as [interpreting higher-order effects](https://hackage.haskell.org/package/cleff-0.1.0.0/candidate/docs/Cleff.html#g:6), with arguably even less boilerplate than `polysemy`.
[In microbenchmarks](#benchmarks), `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](https://github.com/arybczak/effectful/blob/master/effectful-core/src/Effectful/Reader/Static.hs)), `cleff` focuses on balancing expressivity and performance. If you would like minimal performance overhead, consider [`effectful`].

[In terms of performance](#benchmarks), `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](https://github.com/arybczak/effectful/blob/master/effectful-core/src/Effectful/Reader.hs)), `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`].
### Low-boilerplate

In conclusion, `cleff` is an effect library that tries to find a good balance between simplicity, performance, and expressivity.
`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][alexis-talk-2], and [more so when interacting with `IO`][readert]. 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`](https://hackage.haskell.org/package/polysemy-1.7.1.0/docs/Polysemy.html#g:16) 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

The classical `Teletype` effect:
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:

```haskell
import Cleff
Expand All @@ -32,16 +42,19 @@ 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
Expand All @@ -50,6 +63,8 @@ runTeletypePure tty = fmap (reverse . snd)
ReadTTY -> fromMaybe "" <$> input
WriteTTY msg -> output msg

-- Using the effect

echo :: Teletype :> es => Eff es ()
echo = do
x <- readTTY
Expand All @@ -76,7 +91,12 @@ These are the results of the [effect-zoo](https://github.com/ocharles/effect-zoo

## References

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

Papers:

- [Extensible Effect: An Alternative to Monad Transformers](https://okmij.org/ftp/Haskell/extensible/exteff.pdf) by Oleg Kiselyov, Amr Sabry, and Cameron Swords.
- [Freer Monads, More Extensible Effects](https://okmij.org/ftp/Haskell/extensible/more.pdf) by Oleg Kiselyov, and Hiromi Ishii.

Libraries:

Expand All @@ -88,13 +108,14 @@ Libraries:
Talks:

- [Effects for Less][alexis-talk] by Alexis King.
- [Unresolved challenges of scoped effects, and what that means for `eff`](https://www.twitch.tv/videos/1163853841) by Alexis King.
- [Unresolved challenges of scoped effects, and what that means for `eff`][alexis-talk-2] by Alexis King.

Blog posts:

- [Asynchronous Exception Handling in Haskell](https://www.fpcomplete.com/blog/2018/04/async-exception-handling-haskell/) by Michael Snoyman.
- [Polysemy: Mea Culpa](https://reasonablypolymorphic.com/blog/mea-culpa/) by Sandy Maguire.
- [Polysemy Internals: The Effect-Interpreter Effect](https://reasonablypolymorphic.com/blog/tactics/) by Sandy Maguire.
- [ReaderT design pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/) by Michael Snoyman.
- [ReaderT design pattern][readert] by Michael Snoyman.
- [Safe exception handling](https://www.fpcomplete.com/haskell/tutorial/exceptions/) by Michael Snoyman.

[`polysemy`]: https://hackage.haskell.org/package/polysemy
Expand All @@ -103,3 +124,5 @@ Blog posts:
[`eff`]: https://github.com/hasura/eff
[`freer-simple`]: https://hackage.haskell.org/package/freer-simple
[alexis-talk]: https://www.youtube.com/watch?v=0jI-AlWEwYI
[alexis-talk-2]: https://www.twitch.tv/videos/1163853841
[readert]: https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/

0 comments on commit ce68248

Please sign in to comment.