TL; DR Monad 没有它的包装值并不是很特别,你可以将它建模为一个列表。
有一种东西叫做Free
单子。它很有用,因为它在某种意义上是所有其他 monad 的良好代表——如果你能理解Free
monad 在某些情况下的行为,你就会很好地了解Monad
s 通常在那里的行为。
看起来像这样
data Free f a = Pure a
| Free (f (Free f a))
并且无论何时f
是 a Functor
,Free f
都是Monad
instance Functor f => Monad (Free f) where
return = Pure
Pure a >>= f = f a
Free w >>= f = Free (fmap (>>= f) w)
那么当a
总是时会发生什么()
?我们不再需要a
参数
data Freed f = Stop
| Freed (f (Freed f))
显然,这不再是 aMonad
了,因为它的种类(类型类型)错误。
Monad f ===> f :: * -> *
Freed f :: *
但是我们仍然可以通过去掉部分来定义类似Monad
ic 功能的东西a
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)
看起来是(并且是!) a Monoid
。
instance Functor f => Monoid (Freed f) where
mempty = returned
mappend = bound
并且Monoid
s 最初可以通过列表建模。我们使用列表的通用属性 Monoid
,如果我们有一个函数Monoid m => (a -> m)
,那么我们可以将一个列表[a]
变成一个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)
因此,对于您的机器人,我们只需使用动作列表即可
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
]
现在,当我们将其转换为时,IO
我们清楚地将这个方向列表转换为 a Monad
,我们可以看到,将我们的首字母Monoid
发送到Freed
,然后将Freed f
其视为Free f ()
并解释为我们想要Monad
的动作的首字母。IO
但很明显,如果您不使用“包装”值,那么您并没有真正使用Monad
结构。你也可以有一个清单。