Given a monadic operation
op :: forall a. (M a, ..., M a) -> M a
then it is algebraic if it satisfies the following law:
op (p1, ..., pn) >>= k = op (p1 >>= k, ..., pn >>= k)
In other words, >>=
distributes over the operation.
Well, in the absolute simplest case, algebraic operations are operations
where the monad type m
only appears in the return type, not in any of the arguments.
But it turns out that if you have an expression like
(a <|> b) >>= f
where m is a monad like []
, then it’s always equivalent to
(a >>= f) <|> (b >>= f)
which means it is still algebraic
In contrast, most other common operations where m
appears in the arguments are not algebraic, as they do not satisfy the above law.
Consider, for example, local and catch, which have the following types:
local :: MonadReader r m => (r -> r) -> m a -> m a
catch :: MonadError e m => m a -> (e -> m a) -> m a
If those operations were algebraic, the following laws would have to hold:
local f m >>= g == local f (m >>= g)
catch m f >>= g == catch (m >>= g) (f >=> g)
However, it is easy to see that these laws do not hold.
For example, local (+1) m >>= f
increments the reader context by 1 inside m but not inside f,
whereas local (+1) (m >>= f)
uses the incremented context inside both m and f.
If you have an stack of StateT
and ExceptT
together, algebraic operations (e.g. get
, put
, throw
) are not affected by the order of composition.
But on non-algebraic operations (e.g. catch
) the semantic vary depending on which order you stack the transformers.
Example: https://gist.github.com/monadplus/97c1f47d03689cee903f32f7e8dadf25
https://www.reddit.com/r/haskell/comments/ej8fme/unordered_effects/fd00mk2/