polysemy
Higher-order, low-boilerplate, zero-cost free monads.
https://github.com/isovector/polysemy#readme
| Version on this page: | 1.0.0.0 |
| LTS Haskell 24.18: | 1.9.2.0@rev:5 |
| Stackage Nightly 2025-11-01: | 1.9.2.0@rev:5 |
| Latest on Hackage: | 1.9.2.0@rev:5 |
polysemy-1.0.0.0@sha256:5a4596f7ff06db81fc7e2f121fbe481871c348d3c5481297c6af983b728b075c,5509Module documentation for 1.0.0.0
- Polysemy
- Polysemy.Async
- Polysemy.Embed
- Polysemy.Error
- Polysemy.Fixpoint
- Polysemy.IO
- Polysemy.Input
- Polysemy.Internal
- Polysemy.NonDet
- Polysemy.Output
- Polysemy.Reader
- Polysemy.Resource
- Polysemy.State
- Polysemy.Trace
- Polysemy.Writer
polysemy
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
bracketandlocalas 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.
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 (Lift 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 = (runM .@ lowerResource .@@ lowerError @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.Internalsomewhere in its dependency tree (sufficient toimport Polysemy)
- The module you want to be optimized needs to import
- GHC Flags
-Oor-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)
Changes
Changelog for polysemy
1.0.0.0 (2019-07-24)
Breaking Changes
- Renamed
LifttoEmbed(thanks to @googleson78) - Renamed
runAsyncInIOtolowerAsync - Renamed
runAsynctoasyncToIO - Renamed
runBatchOutputtorunOutputBatched - Renamed
runConstInputtorunInputConst - Renamed
runEmbedtorunEmbedded(thanks to @googleson78) - Renamed
runEmbeddedtolowerEmbedded - Renamed
runErrorAsAnothertomapError - Renamed
runErrorInIOtolowerError - Renamed
runFoldMapOutputtorunOutputMonoid - Renamed
runIOtoembedToMonadIO - Renamed
runIgnoringOutputtoignoreOutput - Renamed
runIgnoringTracetoignoreTrace - Renamed
runInputAsReadertoinputToReader - Renamed
runListInputtorunInputList - Renamed
runMonadicInputtorunInputSem - Renamed
runOutputAsListtorunOutputList - Renamed
runOutputAsTracetooutputToTrace - Renamed
runOutputAsWritertooutputToWriter - Renamed
runResourceBasetoresourceToIO - Renamed
runResourceInIOtolowerResource - Renamed
runStateInIOReftorunStateIORef - Renamed
runTraceAsListtorunTraceList - Renamed
runTraceAsOutputtotraceToOutput - Renamed
runTraceIOtotraceToIO - Renamed
sendMtoembed(thanks to @googleson78) - The
NonDeteffect will no longer perform effects in untaken branches (thanks to @KingoftheHomeless)
Other Changes
- Added
evalStateandevalLazyState - Added
runNonDetMaybe(thanks to @KingoftheHomeless) - Added
nonDetToMaybe(thanks to @KingoftheHomeless) - Haddock documentation for smart constructors generated via
makeSemwill no longer have weird variable names (thanks to @TheMatten)
0.7.0.0 (2019-07-08)
Breaking Changes
- Added a
Passconstructor toWriter(thanks to @KingoftheHomeless) - Fixed a bug in
runWriterwhere the MTL semantics wouldn’t be respected (thanks to @KingoftheHomeless) - Removed the
Censorconstructor ofWriter(thanks to @KingoftheHomeless) - Renamed
YotoWeaving - Changed the visible type applications for
asks,gets, andrunErrorAsAnother
Other Changes
- Fixed haddock generation
0.6.0.0 (2019-07-04)
Breaking Changes
- Changed the type of
runBatchOutputto be more useful (thanks to @Infinisil)
Other Changes
- THE ERROR MESSAGES ARE SO MUCH BETTER :party: :party: :party:
- Added
runEmbeddedtoPolysemy.IO - Added
runOutputAsListtoPolysemy.Output(thanks to @googleson78) - Asymptotically improved the performance of
runTraceAsList(thanks to @googleson78)
0.5.1.0 (2019-06-28)
- New combinators for
Polysemy.Error:fromEitherandfromEitherM
0.5.0.1 (2019-06-27)
- Fixed a bug where
interceptandinterceptHwouldn’t correctly handle higher-order effects
0.5.0.0 (2019-06-26)
Breaking Changes
- Removed the internal
Effectmachinery
New Effects and Interpretations
- New effect;
Async, for describing asynchronous computations - New interpretation for
Resource:runResourceBase, which can lowerResourceeffects without giving a lowering natural transformation - New interpretation for
Trace:runTraceAsList - New combinator:
withLowerToIO, which is capable of transformingIO-invariant functions as effects.
Other Changes
- Lots of hard work on the package and CI infrastructure to make it green on GHC 8.4.4 (thanks to @jkachmar)
- Changed the order of the types for
runMonadicInputto be more helpful (thanks to @tempname11) - Improved the error machinery to be more selective about when it runs
- Factored out the TH into a common library for third-party consumers
0.4.0.0 (2019-06-12)
Breaking Changes
- Renamed
runResourcetorunResourceInIO
Other Changes
- Added
runResource, which runs aResourcepurely - Added
onException,finallyandbracketOnErrortoResource - Added a new function,
runResourcewhich performs bracketing for pure code
0.3.0.1 (2019-06-09)
- Fixed a type error in the benchmark caused by deprecation of
Semantic
0.3.0.0 (2019-06-01)
Breaking Changes
- Removed all deprecated names
- Moved
Randomeffect topolysemy-zoo
Other Changes
makeSemcan now be used to create term-level operators (thanks to @TheMatten)
0.2.2.0 (2019-05-30)
- Added
getInspectorTto theTacticalfunctions, which allows polysemy code to be run in external callbacks - A complete rewrite of
Polysemy.Internal.TH.Effect(thanks to @TheMatten) - Fixed a bug in the TH generation of effects where the splices could contain usages of effects that were ambiguous
0.2.1.0 (2019-05-27)
- Fixed a bug in the
Alternativeinstance forSem, where it would choose the last success instead of the first - Added
MonadPlusandMonadFailinstances forSem
0.2.0.0 (2019-05-23)
Breaking Changes
- Lower precedence of
.@and.@@to 8, from 9
Other Changes
- Fixed a serious bug in
interpretHand friends, where higher-order effects would always be run with the current interpreter. - Users need no longer require
inlineRecursiveCalls— thepolysemy-plugin-0.2.0.0will do it automatically when compiling with-O - Deprecated
inlineRecursiveCalls; slated for removal in the next version
0.1.2.1 (2019-05-18)
- Give explicit package bounds for dependencies
- Haddock improvements
- Remove
Typeablemachinery fromPolysemy.Internal.Union(thanks to @googleson78)
0.1.2.0 (2019-04-26)
runInputAsReader,runTraceAsOutputandrunOutputAsWriterhave more generalized types- Added
runStateInIO - Added
runOutputAsTrace - Added
Members(thanks to @TheMatten)
0.1.1.0 (2019-04-14)
- Added
runIOinterpretation (thanks to @adamConnerSax) - Minor documentation fixes
0.1.0.0 (2019-04-11)
- Initial release
Unreleased changes
- Changed the tyvars of
fromEitherM,runErrorAsAnother,runEmbedded,asksandgets