这是我的鸡蛋包装厂:

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

如何链接新的启用了writer的add函数?有更好的方法吗?

最佳答案

在第一个示例中,表达式中的第二个>>=用于MonadMaybe实例,而在第二个示例中,它来自MonadWriter实例。具体来说,在第一个示例中>>=期望一个类型为Carton -> Maybe Carton的函数,例如add 2,而在第二个示例中>>=期望一个类型为Maybe Carton -> Writer [String] (Maybe Carton)的函数。在这两个示例中,pure (Carton 2) >> = add 4都可以工作,因为pure (Carton 2)的类型为Maybe Carton,而add 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

您的示例发生了一些变化:
  • MaybeT m a是monad转换器。在此示例中,mWriter [String]aCarton。要运行所有内容,我们首先使用runMaybeT,它为您提供Writer [String] (Maybe Carton),然后像在示例中所做的那样,对它调用runWriter
  • 要在Writer中使用MaybeT (Writer [String])函数,我们需要对其进行lift。例如lift $ tell ["something"]
  • return carton用于返回Just Carton,而mzero用于返回Nothing

  • 最后一件事:在此示例中,我们无法使用Maybe来组成WriterWriterT [String] Maybe Carton,因为当鸡蛋超过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
    

    关于haskell - 一起使用Maybe和Writer,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38194378/

    10-11 20:41