3

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

我传递给的 monadStateT非常简单:

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 中产生了很多信息,即使失败,我也想保留这些信息。目前,我唯一能做的就是将所有内容转换为 aString并创建一个Bad实例,并将String作为参数传递给fail.

我想做的是:

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

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

无论如何我可以实施fail以保留我需要的信息而不必将所有信息转换为String?

我无法相信 Haskell 可以实现的最佳效果是使用show+read将所有信息作为字符串传递给fail.

4

1 回答 1

7

CheckerError的 monad 与 monad 非常相似Either。我将在我的回答中 使用Eithermonad(及其对应的 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

In CheckerMonad,Either是内部单子,这意味着失败将清除整个状态。注意这个运行函数的类型:

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

您要么失败,要么返回一个结果以及截至该点的状态。

CheckerMonad'中,State是内部单子。这意味着即使发生故障,状态也会被保留:

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

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

需要一些练习来培养如何正确订购单子变压器的直觉。这个 Wikibook 页面的Type juggling部分中的图表是一个很好的起点。

此外,最好避免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函数抛出错误。

于 2014-04-17T09:04:27.060 回答