polysemy
Higher-order, low-boilerplate, zero-cost free monads.
https://github.com/isovector/polysemy#readme
Version on this page: | 1.3.0.0@rev:2 |
LTS Haskell 22.34: | 1.9.2.0 |
Stackage Nightly 2024-09-13: | 1.9.2.0 |
Latest on Hackage: | 1.9.2.0 |
polysemy-1.3.0.0@sha256:fa76e96a883fd1c4bdbad792a0a9d88f59f84817651aea5c71d9b4f74e42c5b6,6141
Module documentation for 1.3.0.0
- Polysemy
- Polysemy.Async
- Polysemy.AtomicState
- Polysemy.Bundle
- Polysemy.Embed
- Polysemy.Error
- Polysemy.Fail
- Polysemy.Final
- Polysemy.Fixpoint
- Polysemy.IO
- Polysemy.Input
- Polysemy.Internal
- Polysemy.Internal.Bundle
- Polysemy.Internal.Combinators
- Polysemy.Internal.CustomErrors
- Polysemy.Internal.Fixpoint
- Polysemy.Internal.Forklift
- Polysemy.Internal.Kind
- Polysemy.Internal.NonDet
- Polysemy.Internal.Strategy
- Polysemy.Internal.TH
- Polysemy.Internal.Tactics
- Polysemy.Internal.Union
- Polysemy.Internal.Writer
- Polysemy.Law
- Polysemy.Membership
- Polysemy.NonDet
- Polysemy.Output
- Polysemy.Reader
- Polysemy.Resource
- Polysemy.State
- Polysemy.Tagged
- Polysemy.Trace
- Polysemy.View
- 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
bracket
andlocal
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
- Raghu Kaippully wrote a beginner friendly tutorial.
- Paweł Szulc gave a great talk on how to start thinking about polysemy.
- I’ve given a talk on some of the performance implementation
- I’ve also written some blog posts on other implementation details.
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 toimport Polysemy
)
- The module you want to be optimized needs to import
- 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)
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 Kiselyov and Hiromi Ishii — Freer Monads, More Extensible Effects
- Nicolas Wu, Tom Schrijvers, and Ralf Hinze — Effect Handlers in Scope
- Nicolas Wu and Tom Schrijvers — Fusion for Free: Efficient Algebraic Effect Handlers
- Andy Gill and other contributors —
mtl
- Rob Rix, Patrick Thomson, and other contributors —
fused-effects
- Alexis King and other contributors —
freer-simple
Changes
Changelog for polysemy
1.3.0.0 (2020-02-14)
Breaking Changes
- The semantics for
runNonDet
when<|>
is used inside a higher-order action of another effect has been reverted to that of 1.1.0.0 and earlier. (See issue #246) - Type parameters for
outputToTrace
have been rearranged (thanks to @juanpaucar)
Other Changes
- Added
Bundle
effect, for bundling multiple effects into a single one. - Added
Tagged
effect, for annotating and disambiguating identical effects. - Added
View
effect, anInput
-like effect for caching an expensive computation. - Added
fromException
/Via
andfromExceptionSem
/Via
- Added
note
- Added
catchJust
,try
andtryJust
(thanks to @bolt12) - Using
listen
withrunWriterTVar
orwriterToIO
will no longer delay writing until thelisten
completes. - Added
runStateSTRef
andstateToST
(thanks to @incertia) - Added
execState
andexecLazyState
(thanks to @tjweir) - Added
Polysemy.Law
, which offers machinery for creating laws for effects. - Added
Polysemy.Membership
for retrieving and making use of effect membership proofs.
1.2.3.0 (2019-10-29)
- Polysemy now works on GHC 8.8.1 (thanks to @googleson78 and @sevanspowell)
- Exported
MemberWithError
fromPolysemy
- Added
rewrite
andtransform
interpretation combinators
1.2.2.0 (2019-10-22)
- Fixed a bug in
resourceToIO
andresourceToIOFinal
that prevented the finalizers from being called inBracketOnError
when the computation failed due to aSem
failure - Added
atomicGets
(thanks to @googleson78) - Added
sequenceConcurrently
toPolysemy.Async
(thanks to @spacekitteh)
1.2.1.0 (2019-09-15)
- Added
InterpreterFor
(thanks to @bolt12) - Bumped bounds for first-class-families
1.2.0.0 (2019-09-04)
Breaking Changes
- All
lower-
interpreters have been deprecated, in favor of corresponding-Final
interpreters. runFixpoint
andrunFixpointM
have been deprecated in favor offixpointToFinal
.- The semantics for
runNonDet
when<|>
is used inside a higher-order action of another effect has been changed. - Type variables for certain internal functions,
failToEmbed
, andatomicState'
have been rearranged.
Other changes
- Added
Final
effect, an effect for embedding higher-order actions in the final monad of the effect stack. Any interpreter should use this instead of requiring to be provided an explicit lowering function to the final monad. - Added
Strategy
environment for use together withFinal
- Added
asyncToIOFinal
, a better alternative oflowerAsync
- Added
errorToIOFinal
, a better alternative oflowerError
- Added
fixpointToFinal
, a better alternative ofrunFixpoint
andrunFixpointM
- Added
resourceToIOFinal
, a better alternative oflowerResource
- Added
outputToIOMonoid
andoutputToIOMonoidAssocR
- Added
stateToIO
- Added
atomicStateToIO
- Added
runWriterTVar
,writerToIOFinal
, andwriterToIOAssocRFinal
- Added
writerToEndoWriter
- Added
subsume
operation - Exposed
raiseUnder
/2
/3
inPolysemy
1.1.0.0 (2019-08-15)
Breaking Changes
MonadFail
is now implemented in terms ofFail
, instead ofNonDet
(thanks to @KingoftheHomeless)LastMember
has been removed.withLowerToIO
and all interpreters that make use of it now only requiresMember (Embed IO) r
(thanks to @KingoftheHomeless)State
andWriter
now have better strictness semantics
Other Changes
- Added
AtomicState
effect (thanks to @KingoftheHomeless) - Added
Fail
effect (thanks to @KingoftheHomeless) - Added
runOutputSem
(thanks to @cnr) - Added
modify'
, a strict variant ofmodify
(thanks to @KingoftheHomeless) - Added right-associative variants of
runOutputMonoid
andrunWriter
(thanks to @KingoftheHomeless) - Added
runOutputMonoidIORef
andrunOutputMonoidTVar
(thanks to @KingoftheHomeless) - Improved
Fixpoint
so it won’t always diverge (thanks to @KingoftheHomeless) makeSem
will now complain ifDataKinds
isn’t enabled (thanks to @pepegar)
1.0.0.0 (2019-07-24)
Breaking Changes
- Renamed
Lift
toEmbed
(thanks to @googleson78) - Renamed
runAsyncInIO
tolowerAsync
- Renamed
runAsync
toasyncToIO
- Renamed
runBatchOutput
torunOutputBatched
- Renamed
runConstInput
torunInputConst
- Renamed
runEmbed
torunEmbedded
(thanks to @googleson78) - Renamed
runEmbedded
tolowerEmbedded
- Renamed
runErrorAsAnother
tomapError
- Renamed
runErrorInIO
tolowerError
- Renamed
runFoldMapOutput
torunOutputMonoid
- Renamed
runIO
toembedToMonadIO
- Renamed
runIgnoringOutput
toignoreOutput
- Renamed
runIgnoringTrace
toignoreTrace
- Renamed
runInputAsReader
toinputToReader
- Renamed
runListInput
torunInputList
- Renamed
runMonadicInput
torunInputSem
- Renamed
runOutputAsList
torunOutputList
- Renamed
runOutputAsTrace
tooutputToTrace
- Renamed
runOutputAsWriter
tooutputToWriter
- Renamed
runResourceBase
toresourceToIO
- Renamed
runResourceInIO
tolowerResource
- Renamed
runStateInIORef
torunStateIORef
- Renamed
runTraceAsList
torunTraceList
- Renamed
runTraceAsOutput
totraceToOutput
- Renamed
runTraceIO
totraceToIO
- Renamed
sendM
toembed
(thanks to @googleson78) - The
NonDet
effect is now higher-order (thanks to @KingoftheHomeless)
Other Changes
- Added
evalState
andevalLazyState
- Added
runNonDetMaybe
(thanks to @KingoftheHomeless) - Added
nonDetToError
(thanks to @KingoftheHomeless) - Haddock documentation for smart constructors generated via
makeSem
will no longer have weird variable names (thanks to @TheMatten)
0.7.0.0 (2019-07-08)
Breaking Changes
- Added a
Pass
constructor toWriter
(thanks to @KingoftheHomeless) - Fixed a bug in
runWriter
where the MTL semantics wouldn’t be respected (thanks to @KingoftheHomeless) - Removed the
Censor
constructor ofWriter
(thanks to @KingoftheHomeless) - Renamed
Yo
toWeaving
- 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
runBatchOutput
to be more useful (thanks to @Infinisil)
Other Changes
- THE ERROR MESSAGES ARE SO MUCH BETTER :party: :party: :party:
- Added
runEmbedded
toPolysemy.IO
- Added
runOutputAsList
toPolysemy.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
:fromEither
andfromEitherM
0.5.0.1 (2019-06-27)
- Fixed a bug where
intercept
andinterceptH
wouldn’t correctly handle higher-order effects
0.5.0.0 (2019-06-26)
Breaking Changes
- Removed the internal
Effect
machinery
New Effects and Interpretations
- New effect;
Async
, for describing asynchronous computations - New interpretation for
Resource
:runResourceBase
, which can lowerResource
effects 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
runMonadicInput
to 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
runResource
torunResourceInIO
Other Changes
- Added
runResource
, which runs aResource
purely - Added
onException
,finally
andbracketOnError
toResource
- Added a new function,
runResource
which 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
Random
effect topolysemy-zoo
Other Changes
makeSem
can now be used to create term-level operators (thanks to @TheMatten)
0.2.2.0 (2019-05-30)
- Added
getInspectorT
to theTactical
functions, 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
Alternative
instance forSem
, where it would choose the last success instead of the first - Added
MonadPlus
andMonadFail
instances 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
interpretH
and friends, where higher-order effects would always be run with the current interpreter. - Users need no longer require
inlineRecursiveCalls
— thepolysemy-plugin-0.2.0.0
will 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
Typeable
machinery fromPolysemy.Internal.Union
(thanks to @googleson78)
0.1.2.0 (2019-04-26)
runInputAsReader
,runTraceAsOutput
andrunOutputAsWriter
have more generalized types- Added
runStateInIO
- Added
runOutputAsTrace
- Added
Members
(thanks to @TheMatten)
0.1.1.0 (2019-04-14)
- Added
runIO
interpretation (thanks to @adamConnerSax) - Minor documentation fixes
0.1.0.0 (2019-04-11)
- Initial release
Unreleased changes
- Changed the tyvars of
fromEitherM
,runErrorAsAnother
,runEmbedded
,asks
andgets