这是我的鸡蛋包装厂:
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
函数?有更好的方法吗? 最佳答案
在第一个示例中,表达式中的第二个>>=
用于Monad
的Maybe
实例,而在第二个示例中,它来自Monad
的Writer
实例。具体来说,在第一个示例中>>=
期望一个类型为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
是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
关于haskell - 一起使用Maybe和Writer,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38194378/