我正在编写一些代码,它使用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
这只是Left
和Right
变体。
令我感到困扰的是fail
的定义。在我的计算中,我在这个monad中产生了很多信息,我想在失败的时候保留这些信息。
目前,我唯一能做的就是将所有内容转换为String
并创建一个Bad
实例,并将String
作为参数传递给fail
。
我想做的是:
fail msg = do
info <- getInfoOutOfTheComputation
return $ Bad info
然而,到目前为止我尝试的所有内容都会出现类型错误,可能是因为这会混合使用不同的monad。
我是否可以实施fail
以保留我需要的信息,而无需将所有信息转换为String
?
我无法相信最好的Haskell可以使用show
+ read
将所有信息作为字符串传递给fail
。
答案 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
类型类强制您定义两个错误构造函数noMsg
和strMsg
,这可能对您的类型有意义,也可能没有意义。
您可以使用EitherT
包中的either
代替,这样您就可以使用任何类型作为错误。使用EitherT
时,请使用left
函数抛出错误。