Overview of Polysemy
mastermtl, freer-simple, and fused-effects, offering better composition, less boilerplate, and the ability to use multiple copies of the same effect without functional dependency issues.repository·master·Indexed 22 days ago
https://github.com/polysemy-research/polysemyA high-performance, low-boilerplate effect system library for Haskell that allows developers to separate domain logic from side-effect implementations using an extensible effects approach. It provides tools for defining custom effects via GADTs, interpreting first-order and higher-order effects, and includes the polysemy-plugin to automate the disambiguation of effect constraints and improve type inference.
mtl, freer-simple, and fused-effects, offering better composition, less boilerplate, and the ability to use multiple copies of the same effect without functional dependency issues.The polysemy-plugin helps the compiler resolve ambiguity when an effect is used but its specific type is not explicitly constrained in a way the standard polysemy typechecker can immediately verify.
For example, in a program like:
foo :: Member (State Int) r => Sem r ()
foo = put 10Without the plugin, the compiler might fail because it cannot be certain if you intended to use a different State effect that also satisfies the required type class constraints. The plugin automates this disambiguation when the intent is clear.
To use the polysemy-plugin to disambiguate effect usage in your Polysemy programs, add the plugin to your package configuration using the -fplugin GHC option. This allows the typechecker to automatically resolve 'obvious' effect constraints that would otherwise cause ambiguity errors.
ghc-options: -fplugin=Polysemy.PluginTo ensure type inference performs as well as mtl, use the polysemy-plugin.
polysemy-plugin to your package.yaml or .cabal file's dependencies section.-- In your source file
{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}-- Or in package.yaml / .cabal -- ghc-options: -fplugin=Polysemy.Plugin
The polysemy-plugin only disambiguates effects when there is exactly one relevant constraint in scope. If multiple effects of the same type (e.g., two different State effects) are present in the Members list, the plugin cannot decide which one to use and will not intervene.
In such cases, you must manually resolve the ambiguity using a type application (e.g., @Int).
Ambiguous case (Plugin will not help):
bar :: Members '[ State Int, State Double ] r => Sem r ()
bar = put 10Resolved case (Manual type application):
bar :: Members '[ State Int, State Double ] r => Sem r ()
bar = put @Int 10The project provides Nix configurations for development. You can build the library or the plugin using nix-build or the Flake interface.
Using nix-build:
nix-build -A polysemy
nix-build -A polysemy-pluginUsing Flakes:
nix build
nix build '.#polysemy-plugin'Running a development shell:
nix-shell --pure
nix-shell --pure --run 'cabal v2-haddock polysemy'
nix-shell --pure --run ghcid
# Flake version
nix develop -i
nix develop -i -c haskell-language-server-wrapperTo use Polysemy effectively, add the following extensions to your project configuration (e.g., package.yaml or .cabal):
ghc-options: -O2 -flate-specialise -fspecialise-aggressively
default-extensions:
- DataKinds
- FlexibleContexts
- GADTs
- LambdaCase
- PolyKinds
- RankNTypes
- ScopedTypeVariables
- TypeApplications
- TypeOperators
- TypeFamiliesPolysemy provides helpful error messages when using the wrong combinator.
If you attempt to use interpret on a higher-order effect (like Resource), the error message will explicitly state that interpret only works with first-order effects and suggest using interpretH instead.
Example Error:
• 'Resource' is higher-order, but 'interpret' can help only
with first-order effects.
Fix:
use 'interpretH' instead.Polysemy supports higher-order effects such as Resource (for bracket) and Error. When using these, you must use the appropriate final interpreters (e.g., resourceToIOFinal, errorToIOFinal) to run the effect stack to IO.
{-# LANGUAGE TemplateHaskell, LambdaCase, BlockArguments, GADTs
, FlexibleContexts, TypeOperators, DataKinds, PolyKinds
, TypeApplications #-}
import Polysemy
import Polysemy.Input
import Polysemy.Output
import Polysemy.Error
import Polysemy.Resource
-- Assuming Teletype is defined as in the previous example
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
$ programTo create a new effect, define a GADT representing the operations and use makeSem to generate the effect functions. You can then write interpreters using interpret (for first-order effects) or interpretH (for higher-order effects like bracket or local).
Example of a Teletype effect with a pure interpreter and an IO interpreter:
{-# LANGUAGE TemplateHaskell, LambdaCase, BlockArguments, GADTs
, FlexibleContexts, TypeOperators, DataKinds, PolyKinds, ScopedTypeVariables #-}
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
-- Usage in a program
echo :: Member Teletype r => Sem r ()
echo = do
i <- readTTY
case i of
"" -> pure ()
_ -> writeTTY i >> echo
main :: IO ()
main = runM . teletypeToIO $ echo