polysemy

:gemini: higher-order, no-boilerplate, zero-cost monads

  • 所有者: polysemy-research/polysemy
  • 平台:
  • 許可證: BSD 3-Clause "New" or "Revised" License
  • 分類:
  • 主題:
  • 喜歡:
    0
      比較:

Github星跟蹤圖

polysemy

Build Status
Hackage
Hackage
Zulip chat

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.

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

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
,
the ReaderT
pattern
and
nicely solves the trouble with typed
errors
.

Concerned about type inference? Check out
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:

{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}

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
    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 GHC1 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

Examples

Make sure you read the Necessary Language
Extensions

before trying these yourself!

Teletype effect:

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

import Polysemy
import Polysemy.Input
import Polysemy.Output

data Teletype m a where
  ReadTTY  :: Teletype m String
  WriteTTY :: String -> Teletype m ()

makeSem ''Teletype

teletypeToIO :: Member (Embed IO) r => Sem (Teletype ': r) a -> Sem r a
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
      ReadTTY -> maybe "" id <$> input
      WriteTTY msg -> output msg


echo :: Member Teletype r => Sem r ()
echo = do
  i <- readTTY
  case i of
    "" -> pure ()
    _  -> writeTTY i >> echo


-- Let's pretend
echoPure :: [String] -> Sem '[] ([String], ())
echoPure = flip runTeletypePure echo

pureOutput :: [String] -> [String]
pureOutput = fst . run . echoPure

-- echo forever
main :: IO ()
main = runM . teletypeToIO $ echo

Resource effect:

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

import Polysemy
import Polysemy.Input
import Polysemy.Output
import Polysemy.Error
import Polysemy.Resource

-- Using Teletype effect from above

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"

main :: IO (Either CustomException ())
main =
    runFinal
  . embedToFinal @IO
  . resourceToIOFinal
  . errorToIOFinal @CustomException
  . teletypeToIO
  $ program

Easy.

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:

runResource
    :: forall r a
     . Sem (Resource ': r) a
    -> Sem r a
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

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.

  ghc-options: -O2 -flate-specialise -fspecialise-aggressively
  default-extensions:
    - DataKinds
    - FlexibleContexts
    - GADTs
    - LambdaCase
    - PolyKinds
    - RankNTypes
    - ScopedTypeVariables
    - TypeApplications
    - TypeOperators
    - 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)

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:

主要指標

概覽
名稱與所有者polysemy-research/polysemy
主編程語言Haskell
編程語言Haskell (語言數: 2)
平台
許可證BSD 3-Clause "New" or "Revised" License
所有者活动
創建於2019-02-13 20:53:25
推送於2025-03-15 12:15:30
最后一次提交2025-03-15 13:15:08
發布數12
最新版本名稱1.9.2.0 (發布於 2024-06-03 20:58:34)
第一版名稱v1.5.0.0 (發布於 )
用户参与
星數1.1k
關注者數28
派生數76
提交數559
已啟用問題?
問題數250
打開的問題數44
拉請求數196
打開的拉請求數0
關閉的拉請求數46
项目设置
已啟用Wiki?
已存檔?
是復刻?
已鎖定?
是鏡像?
是私有?