我有一个很长的递归函数,它可以操作所有不同的数据形式。函数的返回类型是Maybe
,其中Nothing
表示失败。如果任何子调用失败,递归调用将失败,因此函数的每种情况下的计算都在do块中完成。
到目前为止我的实现肯定是错误的,因为我给它的输入应该成功,并且它失败了,即评估为Nothing
。但是,我无法确定哪个子查询正在评估Nothing
。为了确定这发生的位置,我想跟踪所有的递归调用。我有一个想法,如果我可以在trace
的{{1}} Maybe
(即bind
)中插入(>>=)
来电,我就不必插入{{1}进入函数的所有情况。
为了实现这个想法,我只是复制了trace
类型的实现及其Maybe
实例,并按照我的描述对其进行了修改:我在绑定函数中插入了对Monad
的调用
trace
然后我几乎只能更改大函数的返回类型,虽然我不得不改变一些事情,我的代码假设data TraceMaybe a = TNothing | TJust a
deriving (Eq, Ord, Show)
tracet :: TraceMaybe a -> TraceMaybe a
tracet TNothing = trace "monad failed" TNothing
tracet x = trace "monad good" x
instance Monad TraceMaybe where
(TJust x) >>= k = tracet $ k x
TNothing >>= _ = TNothing
(TJust _) >> k = tracet k
TNothing >> _ = TNothing
return = TJust
fail m = TNothing
实际上是Monad
(Maybe
,例如),
我的问题是:是否有一种更优雅,更具创意的方式来创建一个只有稍微修改过的Maybe版本?我听说过“catMaybes
变形金刚”,但我见过的例子似乎并不完全相关。
我想知道是否有某种方法可以将Writer和Maybe结合起来。
答案 0 :(得分:2)
是的,您可以合并Writer
和Maybe
来解决您的问题。
使用monad变形金刚,订单通常很重要。哪个应该是基地monad,哪个是变压器?这里Writer
应该是基础monad,因为否则失败会消除日志(参见this other answer)。
import Control.Monad
import Control.Monad.Writer.Strict
import Control.Monad.Trans.Maybe
computation :: Int -> MaybeT (Writer [String]) Int
computation i = do
foo <- return 5
lift $ tell ["This is a log message"]
if i == 3
then mzero -- from MonadPlus, of which MaybeT is an instance
else return $ i + foo
*Main> runWriter . runMaybeT $ computation 3
(Nothing,["This is a log message"])
*Main> runWriter . runMaybeT $ computation 2
(Just 7,["This is a log message"])
对变形金刚的一个很好的介绍是论文"Monad Transformers Step by Step"。
mtl
包有类型类,可以让你避免在许多情况下使用lift
代码,并且还可以编写更多通用签名(说&#34; monad应该有{{ 1}}功能&#34;而不必指定确切的monad堆栈。)
MonadWriter
当日志消息的数量增加时,使用列表作为computation' :: (MonadWriter [String] m) => Int -> MaybeT m Int
computation' i = do
foo <- return 5
tell ["This is a log message"] -- no explicit lift
if i == 3
then mzero
else return $ i + foo
的幺半群用户变得效率低下,因为追加功能成本很高。您可以转向具有更高效附加效果的结构,例如dlist
s。
有时Writer
不方便,因为您只能访问计算的 end 处的日志消息。您可以使用Writer
或conduit
包中的流式monad变换器,以便能够在计算过程中记录消息,而不必强迫您的monad存在于pipes
。< / p>