【问题标题】:Using Maybe and Writer together一起使用 Maybe 和 Writer
【发布时间】:2016-07-05 02:27:44
【问题描述】:

这是我的鸡蛋包装厂:

type Eggs = Int
data Carton = Carton Eggs deriving Show

add :: Eggs -> Carton -> Maybe Carton
add e (Carton c)
    | c + e <= 12 = Just (Carton $ c + e)
    | otherwise = Nothing

main = do
    print $ pure(Carton 2) >>= add 4 >>= add 4 >>= add 3

似乎运行良好,我可以很好地链接 add 函数。

但我想记录每一步添加了多少鸡蛋的日志。所以我这样做:

import Control.Monad.Writer

type Eggs = Int
data Carton = Carton Eggs deriving Show

add :: Eggs -> Carton -> Writer [String] (Maybe Carton)
add e (Carton c)
    | c + e <= 12 = do
        tell ["adding " ++ show e]
        return (Just (Carton $ c + e))
    | otherwise = do
        tell ["cannot add " ++ show e]
        return Nothing

main = do
    let c = add 4 $ Carton 2
    print $ fst $ runWriter c
    mapM_ putStrLn $ snd $ runWriter c

这给了我我想要的东西:我可以看到生成的纸箱和添加 4 个鸡蛋的记录。

但我似乎失去了像以前一样链接add 函数的能力:

let c = pure(Carton 2) >>= add 4 -- works
let c = pure(Carton 2) >>= add 4 >>= add 2 -- does not work

如何链接我的新启用写入器的add 函数?有更好的方法吗?

【问题讨论】:

  • 请注意,将其写成 add 4 &gt;=&gt; add 2 $ Carton 2add 2 &lt;=&lt; add 4 $ Carton 2 或使用不太常见的 &,Carton 2 &amp; add 4 &gt;=&gt; add 2 可以让您省略纯文本。

标签: haskell monads monad-transformers


【解决方案1】:

只需将addMaybeT 组合起来:

import Control.Trans.Monad.Maybe

test = pure (Carton 2) >>= MaybeT . add 3
                       >>= MaybeT . add 4
                       >>= MaybeT . add 5

runTest = do
  print $ fst $ runWriter (runMaybeT test)

完整示例位于:http://lpaste.net/169070

【讨论】:

    【解决方案2】:

    我会将add &c 改为使用MaybeT (Writer [String])

    import Control.Monad.Writer
    import Control.Monad.Trans.Maybe
    
    type Eggs = Int
    data Carton = Carton Eggs deriving Show
    
    main = do
        let c = add 4 $ Carton 2
            (result, log) = runWriter $ runMaybeT c
        print result
        mapM_ putStrLn log
    
    add :: Eggs -> Carton -> MaybeT (Writer [String]) Carton
    add e (Carton c)
        | c + e <= 12 = do
              tell ["adding " ++ show e]
              return $ Carton $ c + e
        | otherwise = do
              tell ["cannot add " ++ show e]
              mzero
    

    这将允许您的原始代码

    pure (Carton 2) >>= add 4 >>= add 2
    

    按预期工作。

    【讨论】:

      【解决方案3】:

      在第一个示例中,表达式中的第二个&gt;&gt;= 用于MaybeMonad 实例,而在第二个示例中,它来自WriterMonad 实例。具体来说,在第一个示例中,&gt;&gt;= 需要一个类型为 Carton -&gt; Maybe Carton 的函数,例如 add 2,而在第二个示例中,&gt;&gt;= 需要一个类型为 Maybe Carton -&gt; Writer [String] (Maybe Carton) 的函数。在这两个示例中,pure (Carton 2)>>=add 4 都有效,因为pure (Carton 2) 的类型为Maybe Carton,而add 4 的类型为Carton -&gt; &lt;something&gt;,所以你没有问题。在表达式中添加另一个&gt;&gt;= 会触发错误,因为在第一个示例中,此&gt;&gt;= 与第一个示例具有相同的类型,而在第二个示例中,它与&gt;&gt;= 不同。一种解决方案是更改add,使其具有Eggs -&gt; Maybe Carton -&gt; Writer [String] (Maybe Carton) 类型:

      add :: Eggs -> Maybe Carton -> Writer [String] (Maybe Carton)
      add e Nothing = return Nothing
      add e (Just (Carton c))
          | c + e <= 12 = do
              tell ["adding " ++ show e]
              return (Just (Carton $ c + e))
          | otherwise = do
              tell ["cannot add " ++ show e]
              return Nothing
      

      注意,这意味着你不能再使用pure (Carton 2),但你需要pure (Just $ Carton 2)

       > pure (Just $ Carton 2) >>= add 2 >>= add 5
       WriterT (Identity (Just (Carton 9),["adding 2","adding 5"]))
      

      也就是说,我建议您使用monad transformers 组合MaybeWriter,因为这是它们的常见用例。您的示例可以重写为

      import Control.Monad.Trans.Maybe
      import Control.Monad.Writer
      
      type Eggs = Int
      data Carton = Carton Eggs deriving Show
      
      add :: Eggs -> Carton -> MaybeT (Writer [String]) Carton
      add e (Carton c)
          | c + e <= 12 = do
              lift $ tell ["adding " ++ show e]
              return (Carton $ c + e)
          | otherwise = do
              lift $ tell ["cannot add " ++ show e]
              mzero
      
      main = do
          let c = return (Carton 2) >>= add 4 >>= add 2
          let result = runWriter $ runMaybeT c
          print $ fst $ result
          mapM_ putStrLn $ snd $ result
      

      您的示例发生了一些变化:

      1. MaybeT m a 是单子变压器。在此示例中,mWriter [String]aCarton。要运行我们首先runMaybeT 的所有内容,它会为您提供Writer [String] (Maybe Carton),然后我们会调用runWriter,就像您在示例中所做的那样。
      2. 要在MaybeT (Writer [String]) 中使用Writer 函数,我们需要lift 它们。例如lift $ tell ["something"]
      3. return carton 用于返回Just Carton,而mzero 用于返回Nothing

      最后一件事:在这个例子中,我们不能用 WriterT [String] Maybe Carton 组合 MaybeWriter,因为当鸡蛋超过 12 个时,runWriterT 将返回 Nothing 并抑制历史记录:

      import Control.Monad
      import Control.Monad.Trans
      import Control.Applicative
      import Control.Monad.Trans.Maybe
      import Control.Monad.Trans.Writer
      
      type Eggs = Int
      data Carton = Carton Eggs deriving Show
      
      add :: Eggs -> Carton -> WriterT [String] Maybe Carton
      add e (Carton c)
          | c + e <= 12 = do
              tell ["adding " ++ show e]
              lift $ Just $ Carton $ c + e
          | otherwise = do
              tell ["cannot add " ++ show e]
              lift Nothing
      
      main = do
          let c = return (Carton 2) >>= add 4 >>= add 20
          case runWriterT c of
            Nothing ->
              print "nothing to print"
            Just (carton, history) -> do
              print carton
              mapM_ putStrLn $ history
      

      【讨论】:

      • 这是我所期待的确切答案。你能举一个使用 monad 转换器的例子吗?
      • 很好的例子。非常感谢你。我似乎可以在没有lift 的情况下运行您的MaybeT (Writer [String]) Carton 示例。
      • tell 适用于更通用的Writer 版本,称为MonadWriterMaybeT 有一个 MonadWriter 实例,这意味着您可以将 tell 直接用于 MaybeT 而不是 lift 使用 Writer 版本。在实例本身中,tell is defined as lift . tell,这是我在这里使用的。我认为明确的组合有助于理解这个例子。
      猜你喜欢
      • 2013-05-21
      • 1970-01-01
      • 2020-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-12
      • 1970-01-01
      相关资源
      最近更新 更多