Reputation:
I've read this Q&A but don't understand the category theory part.
Here is my reasoning so far: When I look at the types
F (a -> b) -> F a -> F b
(a -> M b) -> M a -> M b
a -> F a
a -> M a
the only portion that resembles a monoid at the type level is the type constructor, i.e. the applicative/monadic context:
// binary operation
F -> F -> F
M -> M -> M
// identity element
F
M
So I'd say applicatives/monads are monoidal in terms of their contexts, because they combine two contexts of the same type into one. pure
/return
creates a most minimal context, so we can think of it as the identity context similar to the identity element of a monoid, which creates a "most minimal value".
However, monads/applicatives are not monoidal in their type parameter, because they include a transformation from a
to b
.
I am not sure my reasoning makes any sense. What baffles me is that monoids on the one hand and applicatives/monads on the other combine things differently:
Nothing <> (Just "bar") -- Just "bar"
(++) <$> Nothing <*> (Just "bar") -- Nothing
Nothing >>= (\x -> (Just "bar") >>= (return . (++) x)) -- Nothing
However, I guess the different result values are due to monoids interpret expressions as ordinary values whereas applicatives/monads interpret expressions as computations (one that may fail in the above example).
Now in the aforementioned Q&A is stated monads are monoidal in the category of endofunctors and applicatives are lax monoidal functors. I don't fully understand that but clearly applicatives only partially preserve the monoidal structure whereas monads fully preserve it. What are the practical implications of this difference from a perspective of a functional programmer?
I ask this question as part of the attempt to better understand applicative/monads and what causes there different expressiveness.
Upvotes: 3
Views: 183
Reputation: 120711
We should sort out that we're dealing with three quite different monoids here:
Monoid
class. Those are value-level monoids, i.e. you're combining together Haskell values such as lists.Applicatives are, mathematically speaking, monoidal functors, but the monoid there is nothing you could write a Monoid
instance for; in the Haskell context, it is the type-level monoid constructed by the unit type ()
and the tuple constructor (,)
. (Note that this is only a monoid if you pretend that e.g. (Int, (String, Bool))
and ((Int, String), Bool)
are the same type.)
Those tuples aren't directly visible in the Applicative
class' methods, but that's just because they have been hidden through currying. The more generalizable formulation of the class is
class Functor f => Monoidal f where
funit :: () -> f ()
fzip :: (f a, f b) -> f (a,b)
It's a nice exercise to prove that this is equivalent to the standard Applicative
class.
What characterises a monoidal functor is really that it preserves this (,)
monoid structure whilst doing its functor-mapping, but that doesn't really have much to do with any particular Haskell monoid.
Monads are, as keeps floating around as a semi-humorous buzz term, monoids in the category of endofunctors. But that's yet another monoid we're talking about, namely the monoid of functor-application stacking. Like with Applicative
, the mathematical formulation of the class is a bit different:
class Monoidal m => Monad m where
pure :: a -> m a
join :: m (m a) -> m a
i.e. the monoid here is a kind of composition of applications of m
.
So with that out of the way, it really shouldn't surprise us that you get different behaviour when comparing something involving monads/applicatives with some particular Haskell monoid. There are cases when you get the same behaviour with both, but that can basically be attributed to having used an instance that degenerates the higher-level monoidal structure to something that, when used with a fixed contained-type, is isomorphic to a non-parametric Monoid
instance.
Upvotes: 7