一起使用Maybe和Writer

时间:2016-07-05 02:27:44

标签: haskell monads monad-transformers

这是我的鸡蛋包装工厂:

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

3 个答案:

答案 0 :(得分:5)

只需使用add撰写MaybeT

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

答案 1 :(得分:5)

我要更改add&amp; 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

按预期工作。

答案 2 :(得分:1)

在第一个示例中,表达式中的第二个>>=用于Monad Maybe实例,而在第二个示例中,它来自Monad Writer实例{1}}。具体来说,在第一个示例中,>>=需要Carton -> Maybe Carton类型的函数,如add 2,而在第二个示例>>=中需要类型为Maybe Carton -> Writer [String] (Maybe Carton)的函数。在以下两个示例中,pure (Carton 2)&gt;&gt; = 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. 要在Writer中使用MaybeT (Writer [String])个功能,我们需要lift个。例如lift $ tell ["something"]
  3. return carton用于返回Just Cartonmzero用于返回Nothing
  4. 最后一件事:在这个例子中,我们不能用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