From ce0fa3d19e7daf9ed0f559ea88c53fa914626f20 Mon Sep 17 00:00:00 2001 From: TheMatten Date: Wed, 22 Jul 2020 09:04:01 +0200 Subject: [PATCH] Update README (#364) Update README --- README.md | 229 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 139 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index f54de45f..4acadd3a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

-Polysemy +Polysemy

 

@@ -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 its companion +[`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[1](#fn1) can optimize - away the entire abstraction at compile time. - - -1: 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 + , FlexibleContexts, TypeOperators, DataKinds, PolyKinds #-} import Polysemy import Polysemy.Input @@ -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 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 @@ -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 @@ -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 @@ -173,7 +173,6 @@ main = Easy. - ## Friendly Error Messages Free monad libraries aren't well known for their ease-of-use. But following in @@ -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 @@ -230,22 +228,69 @@ 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 - -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`) -- GHC Flags - - `-O` or `-O2` - - `-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) - +## *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 did nobody notice this?* + +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. ## Acknowledgements, citations, and related work @@ -253,13 +298,18 @@ 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 @@ -269,4 +319,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 -