在第一个示例中,表达式中的第二个>>= 用于Maybe 的Monad 实例,而在第二个示例中,它来自Writer 的Monad 实例。具体来说,在第一个示例中,>>= 需要一个类型为 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 组合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 是单子变压器。在此示例中,m 是 Writer [String],a 是 Carton。要运行我们首先runMaybeT 的所有内容,它会为您提供Writer [String] (Maybe Carton),然后我们会调用runWriter,就像您在示例中所做的那样。
- 要在
MaybeT (Writer [String]) 中使用Writer 函数,我们需要lift 它们。例如lift $ tell ["something"]
-
return carton 用于返回Just Carton,而mzero 用于返回Nothing
最后一件事:在这个例子中,我们不能用 WriterT [String] Maybe Carton 组合 Maybe 和 Writer,因为当鸡蛋超过 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