我正在编写一些代码,使用 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
这只是一个左
和右
变体。
令我困扰的是失败
的定义。在我的计算中,我在这个单子(monad)中产生了大量信息,即使失败,我也想保留这些信息。
目前我唯一能做的就是将所有内容转换为 String
并创建一个 Bad
实例,并将 String
作为参数传递给 失败
。
我想做的是:
fail msg = do
info <- getInfoOutOfTheComputation
return $ Bad info
但是到目前为止我尝试的所有内容都会出现类型错误,可能是因为这会混合不同的单子(monad)。
无论如何,我是否可以实现fail
以保留我需要的信息,而不必将其全部转换为String
?
我不敢相信 Haskell 能实现的最好的结果是使用 show
+read
将所有信息作为字符串传递给 fail
。
最佳答案
您的 CheckerError
monad 与 Either
monad 非常相似。我将在我的答案中使用 Either
monad (及其对应的 monad 转换器 ErrorT
)。
单子(monad)变形器有一个微妙之处:顺序很重要。 “内部”单子(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,这意味着失败将删除整个状态。注意这个 run 函数的类型:
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
函数抛出错误。
关于haskell - 失败时如何保存信息?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23127973/