Haskell 没有包装值的Monad?
大多数monad解释都使用monad封装值的示例。例如,Haskell 没有包装值的Monad?,haskell,functional-programming,monads,Haskell,Functional Programming,Monads,大多数monad解释都使用monad封装值的示例。例如,可能是一个,其中a类型变量是包装的。但我想知道的是,单子从不包任何东西 举一个人为的例子,假设我有一个真实世界的机器人,可以控制,但没有传感器。也许我想这样控制它: robotMovementScript :: RobotMonad () robotMovementScript = do moveLeft 10 moveForward 25 rotate 180 main :: IO () main = liftIO $
可能是一个
,其中a
类型变量是包装的。但我想知道的是,单子从不包任何东西
举一个人为的例子,假设我有一个真实世界的机器人,可以控制,但没有传感器。也许我想这样控制它:
robotMovementScript :: RobotMonad ()
robotMovementScript = do
moveLeft 10
moveForward 25
rotate 180
main :: IO ()
main =
liftIO $ runRobot robotMovementScript connectToRobot
在我们想象的API中,connectorobot
向物理设备返回某种句柄。此连接成为机器人单子的“上下文”。因为我们与机器人的连接永远无法向我们发送值,所以monad的具体类型总是RobotMonad()
一些问题:
我做作的例子似乎正确吗
我是否正确理解单子的“上下文”概念?我将机器人的连接描述为上下文对吗
有一个从未封装过值的monad(比如RobotMonad
)有意义吗?或者这与单子的基本概念相反
幺半群更适合这种应用吗?我可以想象将机器人控制动作与
连接起来。虽然do
符号看起来更具可读性
在monad的定义中,是否有某种东西可以确保类型总是RobotMonad()
我以Data.Binary.Put
为例。它似乎与我的想法相似(或者完全相同?)。但它也是。考虑到这些增加的皱纹和我目前的技能水平,我认为Put
monad可能不是最有启发性的例子
编辑
我实际上不需要构建机器人或这样的API。这个例子完全是捏造的。我只需要一个例子,在这个例子中,永远不会有理由从monad中提取一个值。所以我不是在问解决机器人问题的最简单方法。相反,这个关于没有内在价值的单子的思想实验是为了更好地理解单子
data Useless a = Useless
instance Monad Useless where
return = const Useless
Useless >>= f = Useless
但正如我指出的,这是没有用的
您需要的是编写器
monad,它将一个幺半群包装为一个monad,这样您就可以使用do表示法了。看起来您有一个类型只支持
(>>) :: m a -> m b -> m b
但是您进一步指定您只希望能够使用m()
s。在这种情况下,我会投赞成票
foo = mconcat
[ moveLeft 10
, moveForward 25
, rotate 180]
作为简单的解决方案。另一种选择是做类似的事情
type Robot = Writer [RobotAction]
inj :: RobotAction -> Robot ()
inj = tell . (:[])
runRobot :: Robot a -> [RobotAction]
runRobot = snd . runWriter
foo = runRobot $ do
inj $ moveLeft 10
inj $ moveForward 25
inj $ rotate 180
runRobotT :: (Monad m) => RobotMonadT m () -> RobotHandle -> IO (m ())
使用Writer
monad
不包装该值的问题是
return a >>= f === f a
假设我们有一个monad忽略了这个值,但是包含了其他有趣的信息
newtype Robot a = Robot {unRobot :: [RobotAction]}
addAction :: RobotAction -> Robot a -> Robot b
f a = Robot [a]
现在如果我们忽略这个值
instance Monad Robot where
return = const (Robot [])
a >>= f = a -- never run the function
然后
所以我们没有单子。因此,如果您想让monad有任何有趣的状态,让=
返回false,那么您需要存储该值。TL;没有包装值的Monad博士并不特别,你可以将它作为一个列表进行建模
有一种东西叫做免费的单子。它很有用,因为它在某种意义上是所有其他单子的一个很好的代表——如果你能理解Free
monad在某些情况下的行为,你就能很好地理解monad
s通常在那里的行为
看起来像这样
data Free f a = Pure a
| Free (f (Free f a))
每当f
是函子时,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
总是()
时会发生什么呢?我们不再需要参数了
data Freed f = Stop
| Freed (f (Freed f))
显然,这不能再是Monad
,因为它的种类(类型)不正确
但我们仍然可以通过去掉a
部分来定义类似于Monad
ic的功能
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)
它看起来是(而且是!)一个Monoid
instance Functor f => Monoid (Freed f) where
mempty = returned
mappend = bound
而Monoid
s最初可以通过列表建模。我们使用listMonoid
的通用属性,其中如果我们有一个函数monoidm=>(a->m)
,那么我们可以将list[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)
现在,当我们将其转换为IO
时,我们很清楚地将这个方向列表转换为Monad
,我们可以看到,作为初始Monoid
并将其发送到Freed
,然后将Freed f
视为Free f()
并将其解释为我们想要的IO
操作的初始Monad
但很明显,如果您没有使用“包装”值,那么您就没有真正使用Monad
结构。你最好有一个列表。我将尝试对以下部分给出部分答案:
- 有一个从未封装过值的monad(比如
RobotMonad
)有意义吗?或者这与单子的基本概念相反
- 幺半群更适合这种应用吗?我可以想象将机器人控制动作与
连接起来。虽然do符号看起来更具可读性
- 在monad的定义中,是否有某种东西可以确保类型总是
RobotMonad()
monads的核心操作是monadic绑定操作
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
这意味着一个动作取决于(或可以取决于)前一个动作的值。因此,如果你有一个概念,它本身有时并不包含可以被视为值的东西(即使是在一个复杂的形式中,比如延续单子),单子不是一个好的抽象
如果我们放弃>=
我们基本上只剩下Applicative
。它还允许我们组合动作,但它们的组合不能依赖于前面动作的值
正如您所建议的,还有一个不带值的Applicative
实例:。其a
类型的动作必须是幺半群
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
]
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
runRobot :: RobotMonad () -> RobotHandle -> IO ()
runRobotT :: (Monad m) => RobotMonadT m () -> RobotHandle -> IO (m ())
runRobotT :: (MonadIO m) => RobotMonadT m () -> RobotHandle -> m ()