Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update README #364

Merged
merged 4 commits into from
Jul 22, 2020
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 145 additions & 78 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="https://raw.githubusercontent.com/isovector/polysemy/master/polysemy.png" alt="Polysemy" title="Polysemy">
<img src="https://raw.githubusercontent.com/polysemy-research/polysemy/master/polysemy.png" alt="Polysemy" title="Polysemy">
</p>

<p>&nbsp;</p>
Expand All @@ -19,76 +19,72 @@
>
> Gilbert K. Chesterton


## Overview

`polysemy` is a library for writing high-power, low-boilerplate, zero-cost,
domain specific languages. It allows you to separate your business logic from
your implementation details. And in doing so, `polysemy` lets you turn your
`polysemy` is a library for writing high-power, low-boilerplate domain specific
languages. It allows you to separate your business logic from your
implementation details. And in doing so, `polysemy` lets you turn your
implementation code into reusable library code.

It's like `mtl` but composes better, requires less boilerplate, and avoids the
O(n^2) instances problem.

It's like `freer-simple` but more powerful and 35x faster.
It's like `freer-simple` but more powerful.

It's like `fused-effects` but with an order of magnitude less boilerplate.

Additionally, unlike `mtl`, `polysemy` has no functional dependencies, so you
can use multiple copies of the same effect. This alleviates the need for ~~ugly
hacks~~ band-aids like [classy
lenses](http://hackage.haskell.org/package/lens-4.17.1/docs/Control-Lens-TH.html#v:makeClassy),
the [`ReaderT`
pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern) and
nicely solves the [trouble with typed
errors](https://www.parsonsmatt.org/2018/11/03/trouble_with_typed_errors.html).

Concerned about type inference? Check out
[polysemy-plugin](https://github.com/isovector/polysemy/tree/master/polysemy-plugin),
which should perform just as well as `mtl`'s! Add `polysemy-plugin` to your package.yaml
or .cabal file's dependencies section to use. Then turn it on with a pragma in your source-files:
hacks~~ band-aids like
[classy lenses](http://hackage.haskell.org/package/lens-4.17.1/docs/Control-Lens-TH.html#v:makeClassy),
the
[`ReaderT` pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern)
and nicely solves the
[trouble with typed errors](https://www.parsonsmatt.org/2018/11/03/trouble_with_typed_errors.html).

Concerned about type inference? `polysemy` comes with companion
TheMatten marked this conversation as resolved.
Show resolved Hide resolved
[`polysemy-plugin`](https://github.com/isovector/polysemy/tree/master/polysemy-plugin),
which helps it perform just as well as `mtl`'s! Add `polysemy-plugin` to your
`package.yaml` or `.cabal` file's `dependencies` section to use. Then turn it on with a pragma in your source files:

```haskell
{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}
```
Or by adding `-fplugin=Polysemy.Plugin` to your package.yaml/.cabal file `ghc-options` section.

Or by adding `-fplugin=Polysemy.Plugin` to your `package.yaml`/`.cabal` file `ghc-options` section.

## Features

* *Effects are higher-order,* meaning it's trivial to write `bracket` and `local`
- *Effects are higher-order,* meaning it's trivial to write `bracket` and `local`
as first-class effects.
* *Effects are low-boilerplate,* meaning you can create new effects in a
- *Effects are low-boilerplate,* meaning you can create new effects in a
single-digit number of lines. New interpreters are nothing but functions and
pattern matching.
* *Effects are zero-cost,* meaning that GHC<sup>[1](#fn1)</sup> can optimize
away the entire abstraction at compile time.


<sup><a name="fn1">1</a></sup>: Unfortunately this is not true in GHC 8.6.3, but
will be true in GHC 8.10.1.


## Tutorials and Resources

- Raghu Kaippully wrote a beginner friendly [tutorial](https://haskell-explained.gitlab.io/blog/posts/2019/07/28/polysemy-is-cool-part-1/index.html).
- Paweł Szulc gave a [great talk](https://youtu.be/idU7GdlfP9Q?t=1394) on how to start thinking about polysemy.
- I've given a talk on some of the [performance implementation](https://www.youtube.com/watch?v=-dHFOjcK6pA)
- I've also written [some](http://reasonablypolymorphic.com/blog/freer-higher-order-effects/) [blog posts](http://reasonablypolymorphic.com/blog/tactics/) on other implementation details.

- Raghu Kaippully wrote a beginner friendly
[tutorial](https://haskell-explained.gitlab.io/blog/posts/2019/07/28/polysemy-is-cool-part-1/index.html).
- Paweł Szulc gave a [great talk](https://youtu.be/idU7GdlfP9Q?t=1394) on how
to start thinking about polysemy.
- Sandy Maguire, the author, gave a talk on some of the
[performance implementation](https://www.youtube.com/watch?v=-dHFOjcK6pA)
- He has also written
[some](http://reasonablypolymorphic.com/blog/freer-higher-order-effects/)
[blog posts](http://reasonablypolymorphic.com/blog/tactics/) on other
implementation details.

## Examples

Make sure you read the [Necessary Language
Extensions](https://github.com/isovector/polysemy#necessary-language-extensions)
Make sure you read the
[Necessary Language Extensions](https://github.com/polysemy-research/polysemy#necessary-language-extensions)
before trying these yourself!

Teletype effect:

```haskell
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE LambdaCase, BlockArguments #-}
{-# LANGUAGE GADTs, FlexibleContexts, TypeOperators, DataKinds, PolyKinds #-}
{-# LANGUAGE TemplateHaskell, LambdaCase, BlockArguments, GADTs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this example isn't very helpful, really

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe it's good to have "dumb" example first, not doing much more than interpreting simple effect, as a sort of showcase of mkSem and interpret.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I keep it as it is?

, FlexibleContexts, TypeOperators, DataKinds, PolyKinds #-}

import Polysemy
import Polysemy.Input
Expand All @@ -101,15 +97,19 @@ data Teletype m a where
makeSem ''Teletype

teletypeToIO :: Member (Embed IO) r => Sem (Teletype ': r) a -> Sem r a
teletypeToIO = interpret $ \case
teletypeToIO = interpret \case
TheMatten marked this conversation as resolved.
Show resolved Hide resolved
ReadTTY -> embed getLine
WriteTTY msg -> embed $ putStrLn msg

runTeletypePure :: [String] -> Sem (Teletype ': r) a -> Sem r ([String], a)
runTeletypePure i
= runOutputMonoid pure -- For each WriteTTY in our program, consume an output by appending it to the list in a ([String], a)
. runInputList i -- Treat each element of our list of strings as a line of input
. reinterpret2 \case -- Reinterpret our effect in terms of Input and Output
-- For each WriteTTY in our program, consume an output by appending it to the
-- list in a ([String], a)
= runOutputMonoid pure
-- Treat each element of our list of strings as a line of input
. runInputList i
-- Reinterpret our effect in terms of Input and Output
. reinterpret2 \case
ReadTTY -> maybe "" id <$> input
WriteTTY msg -> output msg

Expand All @@ -134,13 +134,12 @@ main :: IO ()
main = runM . teletypeToIO $ echo
```


Resource effect:

```haskell
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE LambdaCase, BlockArguments #-}
{-# LANGUAGE GADTs, FlexibleContexts, TypeOperators, DataKinds, PolyKinds, TypeApplications #-}
{-# LANGUAGE TemplateHaskell, LambdaCase, BlockArguments, GADTs
, FlexibleContexts, TypeOperators, DataKinds, PolyKinds
, TypeApplications #-}

import Polysemy
import Polysemy.Input
Expand All @@ -153,17 +152,18 @@ import Polysemy.Resource
data CustomException = ThisException | ThatException deriving Show

program :: Members '[Resource, Teletype, Error CustomException] r => Sem r ()
program = catch @CustomException work $ \e -> writeTTY ("Caught " ++ show e)
where work = bracket (readTTY) (const $ writeTTY "exiting bracket") $ \input -> do
writeTTY "entering bracket"
case input of
"explode" -> throw ThisException
"weird stuff" -> writeTTY input >> throw ThatException
_ -> writeTTY input >> writeTTY "no exceptions"
program = catch @CustomException work \e -> writeTTY $ "Caught " ++ show e
where
work = bracket (readTTY) (const $ writeTTY "exiting bracket") \input -> do
writeTTY "entering bracket"
case input of
"explode" -> throw ThisException
"weird stuff" -> writeTTY input *> throw ThatException
_ -> writeTTY input *> writeTTY "no exceptions"

main :: IO (Either CustomException ())
main =
runFinal
main
= runFinal
. embedToFinal @IO
. resourceToIOFinal
. errorToIOFinal @CustomException
Expand All @@ -173,7 +173,6 @@ main =

Easy.


## Friendly Error Messages

Free monad libraries aren't well known for their ease-of-use. But following in
Expand All @@ -194,22 +193,21 @@ runResource = interpret $ \case

makes the helpful suggestion:

```
• 'Resource' is higher-order, but 'interpret' can help only
with first-order effects.
Fix:
use 'interpretH' instead.
• In the expression:
interpret
$ \case
```txt
• 'Resource' is higher-order, but 'interpret' can help only
with first-order effects.
Fix:
use 'interpretH' instead.
• In the expression:
interpret
$ \case
```

Likewise it will give you tips on what to do if you forget a `TypeApplication`
or forget to handle an effect.

Don't like helpful errors? That's OK too --- just flip the `error-messages` flag
and enjoy the raw, unadulterated fury of the typesystem.

Don't like helpful errors? That's OK too - just flip the `error-messages`
flag and enjoy the raw, unadulterated fury of the typesystem.

## Necessary Language Extensions

Expand All @@ -230,36 +228,106 @@ You're going to want to stick all of this into your `package.yaml` file.
- TypeFamilies
```

## Stellar Engineering - Aligning the stars to optimize `polysemy` away
## *What about performance?* ([TL;DR](#tldr))

Previous versions of this `README` mentioned **the library being**
***zero-cost***, as in having no visible effect on performance. While this was
the original motivation and main factor in implementation of this library, it
turned out that
[**optimizations** we depend on](https://reasonablypolymorphic.com/blog/specialization/),
while showing amazing results in small benchmarks, **don't work in
[bigger, multi-module programs](https://github.com/ghc-proposals/ghc-proposals/pull/313#issuecomment-590143835)**,
what greatly limits their usefulness.

What's more interesting though is that
this **isn't a `polysemy`-specific** problem - basically **all popular effects
libraries** ended up being bitten by variation of this problem in one way or
another, resulting in
[visible drop in performance](https://github.com/lexi-lambda/ghc-proposals/blob/delimited-continuation-primops/proposals/0000-delimited-continuation-primops.md#putting-numbers-to-the-cost)
compared to equivalent code without use of effect systems.

*Why nobody noticed this for such a long time?*
TheMatten marked this conversation as resolved.
Show resolved Hide resolved

One factor may be that while GHC's optimizer is
very, very good in general in optimizing all sorts of abstraction, it's
relatively complex and hard to predict - authors of libraries may have not
deemed location of code relevant, even though it had big effect at the end.
The other is that maybe **it doesn't matter as much** as we like to tell
ourselves. Many of these effects
libraries are used in production and they're doing just fine, because maximum
performance usually matters in small, controlled areas of code, that often
don't use features of effect systems at all.

*What can we do about this?*

Luckily, the same person that uncovered this problems proposed a
[solution](https://github.com/lexi-lambda/ghc-proposals/blob/delimited-continuation-primops/proposals/0000-delimited-continuation-primops.md) -
set of primops that will allow interpretation of effects at runtime, with
minimal overhead. It's not *zero-cost* as we hoped for with `polysemy` at
first, but it should have negligible effect on performance in real life and
compared to current solutions, it should be much more predictable and even
resolve some problems with behaviour of
[specific effects](https://github.com/polysemy-research/polysemy/issues/246).
You can try out experimental library that uses proposed features
[here](https://github.com/hasura/eff).

When it comes to `polysemy`, once GHC proposal lands, we consider option of
switching to implementation based on it. This will probably require some
breaking changes, but should resolve performance issues and maybe even make
implementation of higher-order effects easier.

If you're interested in more details, see
Alexis King's
[talk about the problem](https://www.youtube.com/watch?v=0jI-AlWEwYI),
Sandy Maguire's
[followup about how it relates to `polysemy`](https://reasonablypolymorphic.com/blog/mea-culpa/) and
[GHC proposal](https://github.com/ghc-proposals/ghc-proposals/pull/313) that
adds features needed for new type of implementation.

### TL;DR

Basically all current effects libraries (including `polysemy` and
even `mtl`) got performance wrong - **but**, there's ongoing work on extending
GHC with features that will allow for creation of effects implementation with
stable and strong performance. It's what `polysemy` may choose at some point,
but it will probably require few breaking changes.

## Stellar Engineering - Aligning the stars to optimize `polysemy` ~~away~~ better
TheMatten marked this conversation as resolved.
Show resolved Hide resolved

Several things need to be in place to fully realize our performance goals:

- GHC Version
- GHC 8.9+
- Your code
- The module you want to be optimized needs to import `Polysemy.Internal` somewhere in its dependency tree (sufficient to `import Polysemy`)
- The module you want to be optimized needs to import `Polysemy.Internal`
somewhere in its dependency tree (sufficient to `import Polysemy`)
- GHC Flags
- `-O` or `-O2`
- `-flate-specialise` (this should be automatically turned on by the plugin, but it's worth mentioning)
- `-flate-specialise` (this should be automatically turned on by the plugin,
but it's worth mentioning)
- Plugin
- `-fplugin=Polysemy.Plugin`
- Additional concerns:
- additional core passes (turned on by the plugin)


## Acknowledgements, citations, and related work

The following is a non-exhaustive list of people and works that have had a
significant impact, directly or indirectly, on `polysemy`’s design and
implementation:

- Oleg Kiselyov, Amr Sabry, and Cameron Swords — [Extensible Effects: An alternative to monad transfomers][oleg:exteff]
- Oleg Kiselyov and Hiromi Ishii — [Freer Monads, More Extensible Effects][oleg:more]
- Nicolas Wu, Tom Schrijvers, and Ralf Hinze — [Effect Handlers in Scope][wu:scope]
- Nicolas Wu and Tom Schrijvers — [Fusion for Free: Efficient Algebraic Effect Handlers][schrijvers:fusion]
- Andy Gill and other contributors — [`mtl`][hackage:mtl]
- Rob Rix, Patrick Thomson, and other contributors — [`fused-effects`][gh:fused-effects]
- Alexis King and other contributors — [`freer-simple`][gh:freer-simple]
- Oleg Kiselyov, Amr Sabry, and Cameron Swords —
[Extensible Effects: An alternative to monad transfomers][oleg:exteff]
- Oleg Kiselyov and Hiromi Ishii —
[Freer Monads, More Extensible Effects][oleg:more]
- Nicolas Wu, Tom Schrijvers, and Ralf Hinze —
[Effect Handlers in Scope][wu:scope]
- Nicolas Wu and Tom Schrijvers —
[Fusion for Free: Efficient Algebraic Effect Handlers][schrijvers:fusion]
- Andy Gill and other contributors — [`mtl`][hackage:mtl]
- Rob Rix, Patrick Thomson, and other contributors —
[`fused-effects`][gh:fused-effects]
- Alexis King and other contributors — [`freer-simple`][gh:freer-simple]

[docs]: https://hasura.github.io/eff/Control-Effect.html
[gh:fused-effects]: https://github.com/fused-effects/fused-effects
Expand All @@ -269,4 +337,3 @@ implementation:
[oleg:more]: http://okmij.org/ftp/Haskell/extensible/more.pdf
[schrijvers:fusion]: https://people.cs.kuleuven.be/~tom.schrijvers/Research/papers/mpc2015.pdf
[wu:scope]: https://www.cs.ox.ac.uk/people/nicolas.wu/papers/Scope.pdf