失败时如何保存信息?

时间:2014-04-17 08:15:44

标签: haskell monads monad-transformers state-monad

我正在编写一些代码,它使用StateT monad转换器来跟踪某些有状态信息(日志记录等)。

我传递给StateT的monad非常简单:

data CheckerError a = Bad {errorMessage :: Log} | Good a
    deriving (Eq, Show)


instance Monad CheckerError where
    return x = Good x

    fail msg = Bad msg

    (Bad msg) >>= f = Bad msg
    (Good x) >>= f = f x

type CheckerMonad a = StateT CheckerState CheckerError a

这只是LeftRight变体。

令我感到困扰的是fail的定义。在我的计算中,我在这个monad中产生了很多信息,我想在失败的时候保留这些信息。 目前,我唯一能做的就是将所有内容转换为String并创建一个Bad实例,并将String作为参数传递给fail

我想做的是:

fail msg = do
    info <- getInfoOutOfTheComputation
    return $ Bad info

然而,到目前为止我尝试的所有内容都会出现类型错误,可能是因为这会混合使用不同的monad。

我是否可以实施fail以保留我需要的信息,而无需将所有信息转换为String

我无法相信最好的Haskell可以使用show + read将所有信息作为字符串传递给fail

1 个答案:

答案 0 :(得分:7)

你的CheckerError monad与Either monad非常相似。在我的回答中,我将使用Either monad(及其monad变换器对应ErrorT)。

monad trasformers有一个微妙之处:秩序问题。 “内部”monad中的效果优先于“外部”层引起的效果。考虑CheckerMonad的两个替代定义:

import Control.Monad.State
import Control.Monad.Error

type CheckerState = Int     -- dummy definitions for convenience
type CheckerError = String

type CheckerMonad a = StateT CheckerState (Either String) a

type CheckerMonad' a = ErrorT String (State CheckerState) a

CheckerMonad中,Either是内部monad,这意味着失败会消除整个状态。注意这个运行函数的类型:

runCM :: CheckerMonad a -> CheckerState -> Either CheckerError (a,CheckerState)
runCM m s = runStateT m s

你要么失败,要么将结果与状态一起返回到那一点。

CheckerMonad'中,State是内在的monad。这意味着即使出现故障也会保留状态:

runCM' :: CheckerMonad' a -> CheckerState -> (Either CheckerError a,CheckerState)
runCM' m s = runState (runErrorT m) s

返回一对,其中包含截至该点的状态,以及失败或结果。

需要一些练习来培养如何正确订购monad变换器的直觉。 Type juggling this Wikibook page部分中的图表是一个很好的起点。

此外,最好避免直接使用fail,因为它被认为是语言中的一点瑕疵。相反,使用专用函数来抛出错误转换器提供的错误。使用ErrorT或其他MonadError实例时,请使用throwError

sillycomp :: CheckerMonad' Bool
sillycomp = do
    modify (+1)
    s <- get 
    if s == 3
        then throwError "boo"
        else return True

*Main> runCM' sillycomp 2
Loading package transformers-0.3.0.0 ... linking ... done.
Loading package mtl-2.1.2 ... linking ... done.
(Left "boo",3)

*Main> runCM' sillycomp 3
(Right True,4)

ErrorT有时会令人讨厌,因为与Either不同,它需要对错误类型进行Error约束。 Error类型类强制您定义两个错误构造函数noMsgstrMsg,这可能对您的类型有意义,也可能没有意义。

您可以使用EitherT包中的either代替,这样您就可以使用任何类型作为错误。使用EitherT时,请使用left函数抛出错误。