【问题标题】:bind a monadic value (m2 a) inside some other monad m1在其他一些单子 m1 中绑定单子值 (m2 a)
【发布时间】:2016-01-20 23:51:47
【问题描述】:

今天在 Coding Dojo 工作我尝试了以下方法

example :: IO ()
example = do input <- getLine
             parsed <- parseOnly parser input
             ...

其中parseOnly :: Parser a -&gt; Either String a(来自attoparsec)当然编译器抱怨Either ..不是IO ..本质上告诉我我在混合monad。

当然可以解决

             case parseOnly parser input of .. -> ..

我认为这有点不雅。另外我的猜测是其他人早些时候遇到过这个问题,我认为解决方案与 monad 转换器有关,但最后一点我无法拼凑起来。

它也让我想起了liftIO - 但我认为这是相反的方式,它解决了在一些周围的 monad 内部发生的提升 IO 动作的问题(更准确地说是MonadIO - 例如在Snap 内部想打印一些东西到stdout,同时得到一些http)。

更一般地说,这个问题似乎是针对 Monad m1 和(不同的)Monad m2 我该怎么做类似的事情

example = do a <- m1Action
             b <- m2Action
             ..

【问题讨论】:

  • 这不太可能是单子转换器,我不认为。 IO 只能位于单子变压器堆栈的底部。
  • @dfeuer 你确定吗? ExceptT IO e 是一个完全合理的 monad(由转换器构建),Either eIO 动作都可以合理地解除。
  • @DanielWagner,这是一个很好的观点。 IO 仍在底部,因此您必须将整个东西包裹在 runExceptT 或其他东西中,但在里面的东西更方便。 user3237465 已将其转化为答案。

标签: haskell monads monad-transformers


【解决方案1】:

一般来说,你不能。整个 do 块必须是一个特定的 monad(因为 example 需要有一些特定的类型)。如果您可以在 do 块内绑定任意其他 monad,您将拥有unsafePerformIO

Monad 转换器允许您生成一个 monad,它结合了多个其他 monad 可以做的事情。但是您必须决定 所有 do 块中的操作都使用相同的 monad 转换器堆栈来使用它们,它们不是在 do-block 中任意切换 monad 的方法。

您使用case 的解决方案之所以有效,是因为您有一个特定的已知单子(或者),它可以从其中提取值。并非所有 monad 都提供此功能,因此如果不知道所涉及的特定 monad,就不可能构建通用解决方案。这就是为什么 do 块语法没有提供这种快捷方式的原因。

【讨论】:

    【解决方案2】:

    一般来说,monad 转换器用于这种交错。你可以使用ExceptT

    example :: IO (Either String ())
    example = runExceptT $ do
        input <- liftIO getLine
        parsed <- parseOnly parser input
        ...
    

    请注意,对于某些 aparseOnly 必须返回 ExceptT String IO a。或者更好的ExceptT String m a 用于任何m。或者如果你想让parseOnly 返回Either String a 它是

    example :: IO (Either String ())
    example = runExceptT $ do
        input <- lift getLine
        parsed <- ExceptT $ return $ parseOnly parser input
        ...
    

    但我认为你所需要的只是

    eitherToIO :: Either String a -> IO a
    eitherToIO (Left s)  = error s
    eitherToIO (Right x) = return x
    
    parseOnly :: ... -> String -> Either String Int
    
    example :: IO ()
    example = do
        input  <- getLine
        parsed <- eitherToIO $ parseOnly parser input
        ...
    

    【讨论】:

      【解决方案3】:

      你需要做那个表达式类型检查;就像在纯代码中一样。这里,

      ... = do a <- act1  -- m1 monad
               b <- act2  -- m2 monad
               ...
      

      去糖:

      ... = act1 >>= (\a -> act2 >>= \b -> ...)
      

      &gt;&gt;= 是签名:

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

      外层bind 特化为m1,因此它期望括号内的表达式为a -&gt; m1 b,而内层绑定特化为m2,所以表达式内括号的类型为a -&gt; m2 b:

      -- outer bind expects (    \a   ->   m1 b      )
                   act1 >>= (\a -> act2 >>= \b -> ...)
      -- inner bind results (    \a   ->   m2 b      )
      

      为此类型检查,您需要在两者之间签名m2 b -&gt; m1 b的功能;这就是liftm2m1 单子的某些类 所做的:即m1 ~ t m2,其中tMonadTrans 的一个实例:

      lift :: (Monad m, MonadTrans t) => m a -> t m a
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-05
        • 2016-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多