【问题标题】:How to preserve information when failing?失败时如何保存信息?
【发布时间】:2014-04-17 08:15:44
【问题描述】:

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

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

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

【问题讨论】:

  • 简短回答:这不是fail 的用途,所以它当然不起作用。
  • @Carl 然后告诉我我应该如何实现这一点。我想要的是:1)一种在发生不好的事情时“停止”计算的方法 2)保留到目前为止产生的有状态信息。我见过的所有示例都能够实现 1 使用 fail 而未能实现数字 2。目前我想出的唯一事情是:prettyFail = do state &lt;- get; fail $ show state 在所有状态信息中实现 ShowRead,这我认为这不是一个优雅的解决方案(尽管效果很好)。
  • fail 仅用作权宜之计,以支持 do-notation 中的模式匹配失败。

标签: haskell monads monad-transformers state-monad


【解决方案1】:

您的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 是内部单子,这意味着失败将清除整个状态。注意这个运行函数的类型:

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

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

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

另外,最好避免直接使用fail,因为它在语言中被认为有点不妥。相反,请使用错误转换器提供的用于抛出错误的专用函数。使用ErrorTMonadError 的其他实例时,请使用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,这可能对您的类型有意义,也可能没有意义。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-07
    • 2022-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多