4

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

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

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

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

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

第二种解决方案似乎是错误的,尽管我不能完全说明原因。可能是因为它(可能?)还需要将 ParseError 转换为 monad 具有的任何错误类型的参数(除非我们希望要求 monad 实现MonadError ParseError,这似乎会导致大量的簿记)。最后,我似乎不记得看到MonadError过那么多,这表明使用它存在一些问题。

第三种解决方案适用于我的情况,因为管道将成为具有用户指定 monad(IO) 的管道的一部分,该管道不应该关心解析错误(它将网络数据解析为用户产生的格式指定类型)。但它看起来并不那么优雅,并且会再次(可能?)导致在任何其他上下文中使用时会产生大量的簿记。

我还没有真正考虑过最终的解决方案,但它似乎有些令人费解。

我将不胜感激对这个特殊案例的任何想法(如果我偏离并遗漏了一些明显的东西,我一点也不感到惊讶),以及任何(或多或少相关)关于管道错误处理讨论的参考( -core)/导管/interatee 等

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

4

1 回答 1

7

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

  1. /Pipe内的层:EitherTErrorT

    无论是T e (管道abm) r

  2. /Pipe之外的层:EitherTErrorT

    管道 ab (EitherT em) r

你想要前一种方法,它也有一个很好的属性,你可以让它成为 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 即可。

这种方法还有一个优点,就是您可以有选择地将其应用于管道的某些部分,但是您需要在与其他管道组合之前负责处理潜在的异常值。您可以利用这种灵活性为管道的不同阶段使用不同类型的异常值,或者根本不将其用于不会失败的阶段,并避免对这些阶段进行错误检查的开销。

于 2012-07-10T16:33:27.953 回答