2019-05-30 10:01:16 +03:00
< p align = "center" >
< img src = "https://raw.githubusercontent.com/isovector/polysemy/master/polysemy.png" alt = "Polysemy" title = "Polysemy" >
< / p >
< p > < / p >
2019-03-20 06:42:18 +03:00
# polysemy
2019-03-19 06:04:21 +03:00
2019-06-25 00:05:14 +03:00
[![Build Status ](https://api.travis-ci.org/polysemy-research/polysemy.svg?branch=master )](https://travis-ci.org/polysemy-research/polysemy)
2019-05-24 02:24:57 +03:00
[![Hackage ](https://img.shields.io/hackage/v/polysemy.svg?logo=haskell&label=polysemy )](https://hackage.haskell.org/package/polysemy)
[![Hackage ](https://img.shields.io/hackage/v/polysemy-plugin.svg?logo=haskell&label=polysemy-plugin )](https://hackage.haskell.org/package/polysemy-plugin)
2019-04-10 07:12:34 +03:00
2019-04-10 23:11:36 +03:00
## Dedication
> The word 'good' has many meanings. For example, if a man were to shoot his
> grandmother at a range of five hundred yards, I should call him a good shot,
> but not necessarily a good man.
>
> 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
implementation code into reusable library code.
It's like `mtl` but composes better, requires less boilerplate, and avoids the
O(n^2) instances problem.
2019-04-10 23:40:56 +03:00
It's like `freer-simple` but more powerful and 35x faster.
2019-04-10 23:11:36 +03:00
It's like `fused-effects` but with an order of magnitude less boilerplate.
2019-04-28 06:54:33 +03:00
Additionally, unlike `mtl` , `polysemy` has no functional dependencies, so you
can use multiple copies of the same effect. This alleviates the need for ~~ugly
2019-06-25 00:05:14 +03:00
hacks~~ band-aids like [classy
2019-04-28 06:54:33 +03:00
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 ),
2019-05-14 04:16:34 +03:00
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:
```haskell
{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}
```
Or by adding `-fplugin=Polysemy.Plugin` to your package.yaml/.cabal file `ghc-options` section.
2019-04-28 06:54:33 +03:00
2019-04-10 23:11:36 +03:00
## Features
* *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
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.
2019-04-10 23:27:25 +03:00
< sup > < a name = "fn1" > 1< / a > < / sup > : Unfortunately this is not true in GHC 8.6.3, but
2019-05-27 08:16:14 +03:00
will be true in GHC 8.10.1.
2019-04-10 23:11:36 +03:00
2019-09-04 17:36:41 +03:00
2019-11-05 16:01:17 +03:00
## 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.
2019-04-10 23:11:36 +03:00
## Examples
2019-04-19 18:52:43 +03:00
Make sure you read the [Necessary Language
Extensions](https://github.com/isovector/polysemy#necessary-language-extensions)
before trying these yourself!
2019-04-12 20:44:17 +03:00
Teletype effect:
2019-04-10 23:11:36 +03:00
```haskell
2019-04-11 02:11:41 +03:00
{-# LANGUAGE TemplateHaskell #-}
2019-04-30 22:18:46 +03:00
{-# LANGUAGE LambdaCase, BlockArguments #-}
{-# LANGUAGE GADTs, FlexibleContexts, TypeOperators, DataKinds, PolyKinds #-}
2019-04-10 23:11:36 +03:00
import Polysemy
2019-04-12 20:44:17 +03:00
import Polysemy.Input
import Polysemy.Output
2019-04-10 23:11:36 +03:00
2019-04-12 20:44:17 +03:00
data Teletype m a where
ReadTTY :: Teletype m String
WriteTTY :: String -> Teletype m ()
2019-04-10 23:11:36 +03:00
2019-04-12 20:44:17 +03:00
makeSem ''Teletype
2019-04-10 23:11:36 +03:00
2019-07-27 21:53:35 +03:00
teletypeToIO :: Member (Embed IO) r => Sem (Teletype ': r) a -> Sem r a
2019-07-15 19:40:42 +03:00
teletypeToIO = interpret $ \case
2019-07-11 18:02:26 +03:00
ReadTTY -> embed getLine
WriteTTY msg -> embed $ putStrLn msg
2019-04-12 20:44:17 +03:00
2019-04-30 22:18:46 +03:00
runTeletypePure :: [String] -> Sem (Teletype ': r) a -> Sem r ([String], a)
runTeletypePure i
2019-07-15 19:40:42 +03:00
= 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
2019-04-30 22:18:46 +03:00
. reinterpret2 \case -- Reinterpret our effect in terms of Input and Output
2019-04-12 20:44:17 +03:00
ReadTTY -> maybe "" id < $> input
WriteTTY msg -> output msg
echo :: Member Teletype r => Sem r ()
2019-04-30 22:18:46 +03:00
echo = do
i < - readTTY
case i of
"" -> pure ()
_ -> writeTTY i >> echo
2019-04-12 20:44:17 +03:00
-- Let's pretend
2019-04-30 22:18:46 +03:00
echoPure :: [String] -> Sem '[] ([String], ())
echoPure = flip runTeletypePure echo
2019-04-12 20:44:17 +03:00
2019-04-30 22:18:46 +03:00
pureOutput :: [String] -> [String]
pureOutput = fst . run . echoPure
2019-04-12 20:44:17 +03:00
2019-04-30 22:18:46 +03:00
-- echo forever
2019-04-12 20:44:17 +03:00
main :: IO ()
2019-07-15 19:40:42 +03:00
main = runM . teletypeToIO $ echo
2019-04-10 23:11:36 +03:00
```
Resource effect:
```haskell
{-# LANGUAGE TemplateHaskell #-}
2019-04-30 22:18:46 +03:00
{-# LANGUAGE LambdaCase, BlockArguments #-}
{-# LANGUAGE GADTs, FlexibleContexts, TypeOperators, DataKinds, PolyKinds, TypeApplications #-}
2019-04-10 23:11:36 +03:00
2019-04-12 20:44:17 +03:00
import Polysemy
import Polysemy.Input
import Polysemy.Output
import Polysemy.Error
2019-04-30 22:18:46 +03:00
import Polysemy.Resource
2019-04-12 20:44:17 +03:00
2019-04-30 22:18:46 +03:00
-- Using Teletype effect from above
2019-04-12 20:44:17 +03:00
data CustomException = ThisException | ThatException deriving Show
program :: Members '[Resource, Teletype, Error CustomException] r => Sem r ()
2019-05-14 04:17:05 +03:00
program = catch @CustomException work $ \e -> writeTTY ("Caught " ++ show e)
2019-04-12 20:44:17 +03:00
where work = bracket (readTTY) (const $ writeTTY "exiting bracket") $ \input -> do
writeTTY "entering bracket"
2019-04-30 22:18:46 +03:00
case input of
"explode" -> throw ThisException
"weird stuff" -> writeTTY input >> throw ThatException
_ -> writeTTY input >> writeTTY "no exceptions"
2019-04-12 20:44:17 +03:00
2019-04-30 22:18:46 +03:00
main :: IO (Either CustomException ())
2019-08-30 23:38:53 +03:00
main =
runFinal
. embedToFinal @IO
. resourceToIOFinal
. errorToIOFinal @CustomException
. teletypeToIO
$ program
2019-04-10 23:11:36 +03:00
```
Easy.
2019-03-19 06:04:21 +03:00
2019-04-10 23:22:07 +03:00
## Friendly Error Messages
Free monad libraries aren't well known for their ease-of-use. But following in
the shoes of `freer-simple` , `polysemy` takes a serious stance on providing
helpful error messages.
For example, the library exposes both the `interpret` and `interpretH`
combinators. If you use the wrong one, the library's got your back:
```haskell
runResource
:: forall r a
2019-06-12 16:36:08 +03:00
. Sem (Resource ': r) a
2019-04-19 19:13:11 +03:00
-> Sem r a
2019-06-12 16:36:08 +03:00
runResource = interpret $ \case
2019-04-10 23:22:07 +03:00
...
```
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
```
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.
## Necessary Language Extensions
You're going to want to stick all of this into your `package.yaml` file.
```yaml
ghc-options: -O2 -flate-specialise -fspecialise-aggressively
default-extensions:
- DataKinds
- FlexibleContexts
- GADTs
- LambdaCase
- PolyKinds
- RankNTypes
- ScopedTypeVariables
- TypeApplications
- TypeOperators
- TypeFamilies
```
2019-07-24 15:35:44 +03:00
## Stellar Engineering - Aligning the stars to optimize `polysemy` away
2019-11-05 16:01:17 +03:00
Several things need to be in place to fully realize our performance goals:
2019-07-24 15:35:44 +03:00
- 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)