In Haskell, monads are the primary means of structuring effectful computations. In the context of programming, "effects" refer to things like IO, state manipulation, mutable references, exception handling, etc. Most of the time, you’ll use monads like ‘IO‘, ‘State‘, ‘Reader‘, ‘Writer‘, or ‘Except‘ for these purposes, but they can be combined in more advanced ways, as well.
Let’s take a closer look at some advanced usages of monadic effects in Haskell:
1. ‘Monad Transformers‘: Nested monadic effects can often be cumbersome to work with. Monad transformers aim to alleviate this issue by allowing you to "stack" monads, forming an overall composite monad structure that takes the individual monads into account. The basic idea is that you can take a monad ‘m‘, like ‘IO‘ or ‘State s‘, and ’lift’ other monads into the monad transformer stack, thus enabling the composition of various monadic effects.
An example using ‘StateT‘ transformer and ‘IO‘:
import Control.Monad.Trans.State
import Control.Monad.IO.Class
echo :: StateT Int IO ()
echo = do
liftIO $ putStrLn "Please enter a number:"
n <- liftIO readLn
modify (+ n)
s <- get
liftIO $ putStrLn $ "Sum is: " ++ show s
2. ‘Extensible Effects‘: Extensible Effects is an approach to model advanced monadic effects by composing simple, orthogonal pieces into a unified, flexible whole. The key idea is to use a type-level list of effects (typically called ‘Eff‘) and a set of operations called "handlers" to directly represent and manipulate effectful computations. The ‘freer-simple‘ library in Haskell provides an implementation of extensible effects.
An example:
import Control.Monad.Freer
import Control.Monad.Freer.Reader
import Control.Monad.Freer.Writer
myProgram :: Member (Reader Int) r => Member (Writer String) r => Eff r Int
myProgram = do
n <- ask
tell ("Got value: " ++ show n)
return $ n * 2
3. ‘Algebraic Effects‘: Algebraic Effects are a compositional programming model based on algebraic structures called "effects" and "handlers". These structures allow you to freely define, combine and interpret various effects in a modular way. The goal is similar to Extensible Effects, but with a more algebraic underpinning.
It’s great for defining custom effects and composing them with existing ones. The most common library for Algebraic Effects in Haskell is ‘polysemy‘.
Here’s an example of using ‘polysemy‘:
import Polysemy
import Polysemy.Input
import Polysemy.Output
doubleInput :: Members '[Input Int, Output Int] r => Sem r ()
doubleInput = do
n <- input
output (n * 2)
main :: IO ()
main = do
let doubled = run
. runInputArray [1, 2, 3, 4]
. runOutputPure @Int
$ doubleInput
print $ fst doubled
These advanced usages of monadic effects enable more powerful and expressive programs while keeping their structure more maintainable and modular. They allow you to better control and reason about effectful computations in Haskell.