使用管道(-core)的错误处理方法是什么?

时间:2012-07-09 20:49:42

标签: haskell error-handling

我正在为我的一个小项目写一些管道 - core / attoparsec管道。我希望每个解析器都提供一个管道,等待ByteString输入到解析器并产生任何解析的值(重新启动解析器)。如果没有错误处理,它将具有类似

的类型
parserP :: Monad m => Parser a -> Pipe ByteString a m r

现在,我不确定如何处理解析错误。我目前的想法是:

  • 将错误添加到返回类型(即返回Either ParseError r中的值而不只是r
  • 要求monad提供错误处理机制(即要求monad将管道接管以实现MonadError
  • 强制monad提供错误机制,为{em>任何 monad ErrorT e m a提供管道m
  • 添加参数,让用户指定行为(类似于(ParseError -> P.Pipe ByteString a m r),并在解析错误的情况下简单地绑定到这样提供的管道)

第一个解决方案似乎是错误的,因为使用管道的返回类型进行错误处理似乎更像是一个黑客攻击。一方面,它使管道组成更加丑陋,似乎或多或少被最终解决方案所包含(除了可能失去让下游管道能够通过使用tryAwait从错误中恢复并停止等待值的能力?)。

第二种解决方案似乎不对,但我无法完全理解为什么。可能因为它(可能?)还需要一个参数将ParseError转换为monad所具有的任何错误类型(除非我们希望monad实现MonadError ParseError,这似乎会导致很多书保持)。最后,我似乎无法记得看到MonadError那么多,这表明使用它会有一些问题。

第三个解决方案适用于我的情况,因为管道将是管道的一部分,用户指定的monad(IO)不应该关心解析错误(它会将网络数据解析成一种格式到用户指定的类型)。但它看起来并不那么优雅,而且(可能?)会导致在任何其他环境中使用过很多书籍。

我没有真正考虑过最终的解决方案,但似乎有点费解。

对于这个特殊情况的任何想法我都会感激不尽(如果我离开并遗漏了一些明显的东西,我不会感到惊讶),以及对错误处理讨论的任何(或多或少相关)参考在管道(-core)/管道/ interatee等

编辑: 另一种可能性是采取一个单一动作(而不是一个完整的管道),虽然我不太确定它是否可能只是概括,专门化甚至等同于第四个。

1 个答案:

答案 0 :(得分:7)

如果可以,我想我可以通过描述这样的选择来帮助组织每个人对此的想法。你要么:

  1. Pipe / EitherT中的ErrorT层:

    E t(Pipe a b m)r

  2. Pipe / EitherT之外的ErrorT层:

    管道b(EitherT e m)r

  3. 你想要前一种方法,它也有一个很好的属性,你可以把它作为MonadError的一个实例(如果这是你的事情)。

    要理解两种方法之间的区别,第二种方法会在整个管道的层面上抛出错误。第一个允许以单个管道的粒度进行错误处理,并正确处理组合管道。

    现在有些代码。如果你不介意我会使用EitherT,因为我对它更加满意:

    import Control.Error
    import Control.Pipe
    
    type PipeE e a b m r = EitherT e (Pipe a b m) r
    
    runPipeE = runPipe . runEitherT
    
    p1 <?< p2 = EitherT (runEitherT p1 <+< runEitherT p2)
    

    然后只需在PipeE中使用catchT和throwT即可获得内容。

    这种方法有另一个优点,即您可以选择性地将其应用于管道的某些部分,但是在您与其他管道组合之前,您有责任处理潜在的特殊值。您可以使用这种灵活性为管道的不同阶段使用不同类型的异常值,或者根本不使用它来处理不能失败的阶段,并避免错误检查这些阶段的开销。