Monad没有包装值?

大部分monad解释都使用monad包装值的例子。 例如, Maybe a ,其中a类型变量是包装的。 但我想知道monads不会包装任何东西。

对于一个人为的例子,假设我有一个可以控制的现实世界的机器人,但没有传感器。 也许我想要这样控制它:

robotMovementScript :: RobotMonad ()
robotMovementScript = do
  moveLeft 10
  moveForward 25
  rotate 180

main :: IO ()
main = 
  liftIO $ runRobot robotMovementScript connectToRobot

在我们的虚拟API中, connectToRobot返回物理设备的某种句柄。 这个连接成为RobotMonad的“上下文”。 因为我们与机器人的连接永远不会向我们发回值,所以monad的具体类型始终是RobotMonad ()

一些问题:

  • 我做作的例子看起来是对的吗?
  • 我是否正确理解monad的“背景”的概念? 我正确地描述机器人的连接作为上下文吗?
  • 拥有一个monad是否有意义 - 比如RobotMonad - 从不包装价值? 或者这是否违背了monads的基本概念?
  • monoid是否更适合这种应用? 我可以想象用<>连接机器人控制动作。 虽然do标记看起来更具可读性。
  • 在monad的定义中,是否可以确保类型永远是RobotMonad ()
  • 我已经看过Data.Binary.Put作为例子。 它似乎与我所想的相似(或者相同?)。 但它也涉及作家monad和Builder monoid。 考虑到那些额外的皱纹和我目前的技能水平,我认为Put monad可能不是最具启发性的例子。

    编辑

    我实际上并不需要构建一个像这样的机器人或API。 这个例子完全是人为的。 我只是需要一个永远不会有理由将价值从monad中提取出来的例子。 所以我并没有要求解决机器人问题的最简单方法。 相反,这个关于没有内在价值的单子的思想实验是试图更好地理解单子。


    TL;没有包装价值的DR Monad并不是很特别,你可以将所有相同的功率建模为一个列表。

    有一种叫做Free monad的东西。 这很有用,因为它在某种意义上是所有其他monad的优秀代表 - 如果您在某些情况下可以了解Free monad的行为,那么您可以很好地了解Monad的行为。

    它看起来像这样

    data Free f a = Pure a
                  | Free (f (Free f a))
    

    每当fFunctorFree 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))
    

    显然,这不能再是Monad了,因为它有错误的类型(类型)。

    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)
    

    看起来是(而且是!)一个Monoid

    instance Functor f => Monoid (Freed f) where
      mempty  = returned
      mappend = bound
    

    Monoid可以通过列表来初始化。 我们使用列表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我们明确地将这个方向列表转换为Monad并且我们可以看到,将我们的初始Monoid发送给Freed ,然后将Freed f当作Free f ()并将其解释为初始Monad关于我们想要的IO操作。

    但很明显,如果你不使用“包装”值,那么你并没有真正使用Monad结构。 你可能只是有一个列表。


    我会试着对这些部分给出部分答案:

  • 拥有一个monad是否有意义 - 比如RobotMonad - 从不包装价值? 或者这是否违背了monads的基本概念?
  • monoid是否更适合这种应用? 我可以想象用<>连接机器人控制动作。 虽然符号似乎更具可读性。
  • 在monad的定义中,是否可以确保类型永远是RobotMonad ()
  • monad的核心操作是monadic绑定操作

    (>>=) :: (Monad m) => m a -> (a -> m b) -> m b
    

    这意味着一个行动取决于(或可能取决于)前一个行动的价值。 因此,如果你有一个固有的有时并不带有可被视为价值的东西的概念(即使是复杂的形式,如继续单子), monad也不是一个好的抽象概念

    如果我们放弃>>=我们基本上只剩下Applicative 。 它也允许我们编写行动,但它们的组合不能依赖于前面的值。

    还有一个Applicative实例不带值,如您所示:Data.Functor.Constant。 它的类型a行为必须是一个monoid,以便它们可以组合在一起。 这似乎是最接近你想法的概念。 当然,我们可以直接使用Monoid而不是Constant


    也就是说,也许更简单的解决方案是拥有一个monad RobotMonad a ,它具有一个值( RobotMonad a ,它本质上与Writer monad是同构的)。 并声明runRobot需要RobotMonad () ,所以它可以只执行没有值的脚本:

    runRobot :: RobotMonad () -> RobotHandle -> IO ()
    

    这将允许您使用do记号,并与机器人脚本中值的工作。 即使机器人没有传感器,能够传递值也常常有用。 并且扩展这个概念可以让你创建一个monad变换器,比如RobotMonadT ma (类似于WriterT

    runRobotT :: (Monad m) => RobotMonadT m () -> RobotHandle -> IO (m ())
    

    也许

    runRobotT :: (MonadIO m) => RobotMonadT m () -> RobotHandle -> m ()
    

    这将是一个强大的抽象,可以让你将机器人动作与任意monad结合起来。


    好吧

    data Useless a = Useless
    instance Monad Useless where
      return = const Useless
      Useless >>= f = Useless
    

    但正如我所表明的那样,这不是有用的。

    你想要的是Writer monad,它把monoid包装成monad,所以你可以使用符号。

    链接地址: http://www.djcxy.com/p/47669.html

    上一篇: Monad with no wrapped value?

    下一篇: where are they necessary?