Most of the monad explanations use examples where the monad wraps a value. E.g. Maybe a
, where the a
type variable is what\'s wrapped. But I\'m won
TL;DR Monad without its wrapped value isn't very special and you get all the same power modeling it as a list.
There's a thing known as the Free
monad. It's useful because it in some sense is a good representer for all other monads---if you can understand the behavior of the Free
monad in some circumstance you have a good insight into how Monad
s generally will behave there.
It looks like this
data Free f a = Pure a
| Free (f (Free f a))
and whenever f
is a Functor
, Free f
is a Monad
instance Functor f => Monad (Free f) where
return = Pure
Pure a >>= f = f a
Free w >>= f = Free (fmap (>>= f) w)
So what happens when a
is always ()
? We don't need the a
parameter anymore
data Freed f = Stop
| Freed (f (Freed f))
Clearly this cannot be a Monad
anymore as it has the wrong kind (type of types).
Monad f ===> f :: * -> *
Freed f :: *
But we can still define something like Monad
ic functionality onto it by getting rid of the a
parts
returned :: Freed f
returned = Stop
bound :: Functor f -- compare with the Monad definition
=> Freed f -> Freed f -- with all `a`s replaced by ()
-> Freed f
bound Stop k = k Pure () >>= f = f ()
bound (Freed w) k = Free w >>= f =
Freed (fmap (`bound` k) w) Free (fmap (>>= f) w)
-- Also compare with (++)
(++) [] ys = ys
(++) (x:xs) ys = x : ((++) xs ys)
Which looks to be (and is!) a Monoid
.
instance Functor f => Monoid (Freed f) where
mempty = returned
mappend = bound
And Monoid
s can be initially modeled by lists. We use the universal property of the list Monoid
where if we have a function Monoid m => (a -> m)
then we can turn a list [a]
into an m
.
convert :: Monoid m => (a -> m) -> [a] -> m
convert f = foldr mappend mempty . map f
convertFreed :: Functor f => [f ()] -> Freed f
convertFreed = convert go where
go :: Functor f => f () -> Freed f
go w = Freed (const Stop <$> w)
So in the case of your robot, we can get away with just using a list of actions
data Direction = Left | Right | Forward | Back
data ActionF a = Move Direction Double a
| Rotate Double a
deriving ( Functor )
-- and if we're using `ActionF ()` then we might as well do
data Action = Move Direction Double
| Rotate Double
robotMovementScript = [ Move Left 10
, Move Forward 25
, Rotate 180
]
Now when we cast it to IO
we're clearly converting this list of directions into a Monad
and we can see that as taking our initial Monoid
and sending it to Freed
and then treating Freed f
as Free f ()
and interpreting that as an initial Monad
over the IO
actions we want.
But it's clear that if you're not making use of the "wrapped" values then you're not really making use of Monad
structure. You might as well just have a list.