polysemy: monad transformers without the transformer stack
:gemini: higher-order, no-boilerplate monads
At a glance
- What is it?
- polysemy is Sandy Maguire's Haskell library for higher-order effects, where an effect is a GADT interpreted by ordinary functions, so bracket and local become effects in their own right instead of helpers bolted onto a monad transformer. It composes better than mtl, needs no functional dependencies, and requires a set of language extensions and a GHC plugin to keep type inference working.
- Who is it for?
- Adopt polysemy if you are writing Haskell with a stack of effects and want to define your own effect in a few lines, or if you are porting code from a transformer stack and want the same shape without the instance explosion.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly Haskell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Effects as a GADT, interpreted by a function
polysemy is described as a library for writing high-power, low-boilerplate domain specific languages, and its stated purpose is to let you separate your business logic from your implementation details, and in doing so turn your implementation code into reusable library code. The mechanism is the one visible in the Teletype example. An effect is a GADT, so the operations of your effect are constructors of a type-level list, and you declare it with a data declaration and a makeSem call. An interpreter is then a plain function that pattern matches on the effect's operations, and the composition of several effects is expressed as a type-level list of members. The two headline features follow from that shape. Effects are higher-order, which the documentation says makes it trivial to write bracket and local as first-class effects rather than as helpers that need to exist outside the effect system. And effects are low-boilerplate, meaning you can create a new effect in a single-digit number of lines, and a new interpreter is nothing but functions and pattern matching. There is no transformer stack to thread and no type class instance to write.
How it compares with mtl, freer-simple and fused-effects
The README positions the library with three comparisons, and they are worth quoting because they describe the trade precisely. It is like mtl but it composes better, requires less boilerplate, and avoids the O(n^2) instances problem, which is the familiar complaint that a transformer stack needs an instance for every combination of effects you actually use. It is like freer-simple but more powerful, which is the freer family with the higher-order effects added. And it is like fused-effects but with an order of magnitude less boilerplate. The common thread is that all four libraries exist to let a programmer describe an action in terms of effects and then run that action under an interpreter, and they differ in how much machinery is required to get from one to the other. polysemy's bet is that the machinery should be close to zero and that the expressiveness should come from the effect definitions rather than from the plumbing. The accompanying plugin and the error messages discussed below are both consequences of that bet, because a system that leans on type inference has to protect the programmer from the places where inference would otherwise fail.
No functional dependencies, and the band-aids that disappear with them
The most concrete technical claim in the README is that, unlike mtl, polysemy has no functional dependencies, so you can use multiple copies of the same effect. The documentation then lists the problems this alleviates, and it is an unusually useful list because each item is a workaround people carry in real codebases. It removes the need for what the text calls ugly hacks or band-aids such as classy lenses, the ReaderT pattern, and it nicely solves the trouble with typed errors, with links to the relevant write-ups for each. The mechanism behind the improvement is that the effect stack is a type-level list rather than a nested type constructor, so combining two effects does not require the compiler to solve a functional dependency between their associated types. In practice this is the difference between a codebase that accumulates type class instances and constraints as it grows and one that does not, and it is the strongest argument for the library.
Type inference, and the plugin that keeps it working
The library's author is not unaware that a higher-order effect encoding can make type inference harder, and the README addresses it head on with the question, concerned about type inference. The answer is the companion package polysemy-plugin, a GHC plugin that helps polysemy perform as well as mtl in that respect. Using it takes two steps. You add polysemy-plugin to the dependencies section of your package.yaml or .cabal file, and then you turn it on, either with a pragma in your source files:
{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}or by adding -fplugin=Polysemy.Plugin to the ghc-options section of your package.yaml or .cabal file. The pragma approach is the one shown in the example, and it is per file, which means a codebase can adopt the plugin gradually rather than switching the whole project at once. The project name for the plugin package on Hackage is polysemy-plugin, and the tree carries a polysemy-plugin/ directory alongside the main src/ and test/ directories, so the plugin is developed in the same repository rather than as a separate project.
The extension list is the price of admission
The README has a section called Necessary Language Extensions, and the recommended block is worth seeing before you start, because it tells you what kind of project polysemy expects. The ghc-options line is not minimal, it is -O2 with -flate-specialise and -fspecialise-aggressively, which is a performance orientation rather than a requirement. The default-extensions list is the real cost: DataKinds, FlexibleContexts, GADTs, LambdaCase, PolyKinds, RankNTypes, ScopedTypeVariables, TypeApplications and TypeOperators. That is nine extensions, and the examples in the README carry the same set inline, in a LANGUAGE pragma at the top of each file, listing TemplateHaskell, LambdaCase, BlockArguments, GADTs, FlexibleContexts, TypeOperators, DataKinds, PolyKinds and ScopedTypeVariables for the Teletype example, with TypeApplications added for the Resource example. Two of those have real consequences beyond noise. TypeApplications is what allows the type application syntax used in errorToIOFinal @CustomException, and GADTs are what make the effect declarations possible at all. The project suggests putting the whole set in package.yaml so that individual files stay clean.
Error messages that tell you which combinator to use
One feature deserves its own section because it is unusual in a library of this kind. The README notes that free monad libraries are not well known for their ease of use, and that following in the shoes of freer-simple, polysemy takes a serious stance on providing helpful error messages. The example given is precise. The library exposes both the interpret and the interpretH combinators, and if you use the wrong one for a higher-order effect, the error says so. Using interpret where interpretH is needed produces a message stating that the effect is higher-order but interpret can help only with first-order effects, that the fix is to use interpretH instead, and it quotes the expression that triggered the problem. For a library whose whole appeal is that it works out the interpretation for you, this is the right place to spend effort, because a beginner hitting an opaque type error in a higher-order effect system is otherwise stuck.
Where polysemy is the wrong tool
Four limits are worth naming. The first is the release cadence. The newest tags are v1.8.0.0 from 2022-12-22 and v1.7.0.0 from 2021-11-16, with the plugin at plugin-v0.4.2.0 from the same November 2021 date, while the master branch was pushed on 2026-10-10, so the code is being worked on but nothing has been published in years. For a library you intend to depend on, that gap is a supply-chain question, not a philosophical one. The second is the extension burden. Nine default extensions and a compiler plugin is a real cost in a codebase that has to compile on a different toolchain or a different set of policies, and it is the price of the higher-order encoding. The third is that higher-order effects are not free at the type level either. Interpreters for higher-order effects have to thread state through callbacks in ways that are easy to get subtly wrong, and the library helps with error messages rather than with proofs. The fourth is the audience. The resources listed in the README, a beginner tutorial, a porting guide, a talk on how to start thinking about polysemy and talks on its performance implementation, are all written for programmers who already know what a monad transformer stack is. If you are new to effect handling in Haskell, this is a demanding place to start, and the transformer's stack of libraries is the gentler route.
Editorial conclusion
Adopt polysemy if you are writing Haskell with a stack of effects and want to define your own effect in a few lines, or if you are porting code from a transformer stack and want the same shape without the instance explosion. Do not adopt it in a codebase that cannot live with the language extension list and the plugin, because the extensions are not optional in practice, and do not adopt it for a stable product expecting a release cadence, since the newest tag is v1.8.0.0 from 2022-12-22 even though the branch has been touched as recently as 2026-10-10. Before you start, read the porting guide the author wrote for transformer-based code, since that is the path of least resistance.
Frequently asked questions
What is polysemy?
It is a Haskell library for writing high-power, low-boilerplate domain specific languages, separating business logic from implementation details. Its effects are higher-order, so bracket and local can be written as first-class effects, and a new effect takes only a single-digit number of lines to define.
How does polysemy differ from mtl?
The README says it is like mtl but it composes better, requires less boilerplate, and avoids the O(n^2) instances problem. Unlike mtl it has no functional dependencies, so multiple copies of the same effect can be used, which removes the need for workarounds such as classy lenses and the ReaderT pattern.
Do I need a compiler plugin to use polysemy?
Not strictly, but the project ships polysemy-plugin because type inference can suffer. You add it to the dependencies of your package.yaml or .cabal file and enable it with the pragma `{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}` or by adding -fplugin=Polysemy.Plugin to ghc-options.
Which language extensions does polysemy need?
The README lists DataKinds, FlexibleContexts, GADTs, LambdaCase, PolyKinds, RankNTypes, ScopedTypeVariables, TypeApplications and TypeOperators as the default-extensions to add to package.yaml, with the examples also using TemplateHaskell and BlockArguments.
How often is polysemy released?
The latest listed release is v1.8.0.0 from 2022-12-22, after v1.7.0.0 from 2021-11-16, while the master branch was pushed on 2026-10-10. The project is licensed under BSD-3-Clause and is published on Hackage as polysemy and polysemy-plugin.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/polysemy-research-polysemy)Was this useful?