Polysemy: An introduction
I used to teach functional programming using Haskell in an engineering.
I was starting my course by defining functional programming being defined by three interdependent components:
- Types
- Expressions
- Interpretations
I was emphasizing Haskell's precise type system, and all went well until they encountered IO
.
Haskell provides a lot of tools to create a fine grained design for pure computations (with (G)ADTs, types synonyms, etc.), but when it comes to I/O, we have one generic type.
While it may simplify my course, it's not helpful for my production-level products.
A way to mitigate that is to use effects systems, which are design to have small, well-defined effects, grouped in typed.
A widespread implementation of this mechanism is the mtl
, but it has a number of issues (and here).
Since September 2020 I'm using Polysemy (see my feedback one year later).
This log is the first one of a long series going through Polysemy.
I'll use Polysemy 1.7.1.0
with GHC 9.2.5
, I'll drop the code here.
Let's start with a simple example:
displayFile path = do
trace $ "Displaying " <> path
content <- embed $ readFile path
embed $ putStr content
return $ length content
We define a function displayFile
which produce a Sem
expression which:
- Produces an
Int
(the file size) once interpreted - Uses
Trace
andEmbed IO
(a "lifted"Monad
) effects ('[Trace, Embed IO]
is anEffectRow
)
Step by step we do the following things:
- Emit a
Trace
effect withtrace
- Emit an
Embed IO
effect withembed
ofreadFile
- Emit an
Embed IO
effect withembed
ofputStr
- Produce the
content
's length
See the full the code here.