一起使用 Maybe 和 Writer

Using Maybe and Writer together

这是我的鸡蛋包装工厂:

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 函数?有更好的方法吗?

只需将 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

我会将 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

按预期工作。

在第一个示例中,表达式中的第二个 >>= 用于 MaybeMonad 实例,而在第二个示例中它来自 Monad 实例Writer。具体来说,在第一个示例中,>>= 需要一个类型为 Carton -> Maybe Carton 的函数,例如 add 2,而在第二个示例中,>>= 需要一个类型为 Maybe Carton -> Writer [String] (Maybe Carton) 的函数.在这两个示例中,pure (Carton 2)>>=add 4 都有效,因为 pure (Carton 2) 的类型为 Maybe Cartonadd 4 的类型为 Carton -> <something>,因此您没有问题。将另一个 >>= 添加到表达式会触发错误,因为在第一个示例中,此 >>= 与第一个示例具有相同的类型,而在第二个示例中,它与 >>= 不同。一种解决方案是更改 add 使其具有类型 Eggs -> Maybe Carton -> 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 是 monad 转换器。在此示例中,mWriter [String]aCarton。对于 运行 我们首先 runMaybeT 的所有内容,它给你一个 Writer [String] (Maybe Carton),然后我们调用 runWriter 就像你在你的例子中所做的那样。
  2. 要在 MaybeT (Writer [String]) 中使用 Writer 函数,我们需要 lift 它们。例如lift $ tell ["something"]
  3. return carton用于return一个Just Cartonmzero用于returnNothing

最后一件事:在这个例子中,我们不能用 WriterT [String] Maybe Carton 反过来组合 MaybeWriter,因为当有超过 12 个鸡蛋时 runWriterT 会 return 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