这是我的鸡蛋包装工厂:
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
函数?有没有更好的方法呢?
答案 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)
答案 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 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撰写Maybe
和Writer
,因为这是他们的常见用例。您的示例可以重写为
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变换器。在此示例中,m
为Writer [String]
,a
为Carton
。要运行我们首先runMaybeT
的所有内容,这会为您提供Writer [String] (Maybe Carton)
,然后我们就像您在示例中所做的那样调用runWriter
。Writer
中使用MaybeT (Writer [String])
个功能,我们需要lift
个。例如lift $ tell ["something"]
return carton
用于返回Just Carton
,mzero
用于返回Nothing
最后一件事:在这个例子中,我们不能用Maybe
反过来组成Writer
和WriterT [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