【问题标题】:Applicative Parser deriving Alternative without empty应用解析器派生没有空的替代方案
【发布时间】:2018-02-25 08:23:38
【问题描述】:

通过阅读this answer,我了解到在Alternative 中包含empty 在很大程度上是为了使Alternative 成为一个幺半群(因此更强大)的设计决定。在我看来,这也是因为否则你无法为Alternative 表达任何法律。

但是,如果我有一个像这样的通用应用解析器,那就太痛苦了:

newtype Parser err src target = Parser (ExceptT err (State [src]) target)
  deriving (Functor, Applicative, Alternative, Monad, MonadState [src], MonadError err)

显然,我们可以从Control.Applicative 获得与<|>many/some 相同的行为:

option :: Parser e s t -> Parser e s t -> Parser e s t
option parserA parserB = do
  state <- get
  parserA `catchError` \_ -> put state >> parserB

many :: Parser e s t -> Parser e s [t]
many parser = some parser `option` return []

some :: Parser e s t -> Parser e s [t]
some parser = (:) <$> parser <*> many parser

尽管这些都没有使用empty,但似乎我不得不重新实现它们而不是派生Alternative,因为我无法设想一个通用的方法来为它实例化empty(当然,我仍然需要实例化&lt;|&gt; 以在parserA 错误上保存state,但随后我可以免费获得somemanyoptional 和朋友)。

挖掘 Parsec 的源代码,它似乎颠覆了这一点,因为它不允许自定义错误类型(或者至少,Parsec 没有被自定义错误类型参数化):

instance Applicative.Alternative (ParsecT s u m) where
    empty = mzero
    (<|>) = mplus

instance MonadPlus (ParsecT s u m) where
    mzero = parserZero
    mplus p1 p2 = parserPlus p1 p2

-- | @parserZero@ always fails without consuming any input. @parserZero@ is defined
-- equal to the 'mzero' member of the 'MonadPlus' class and to the 'Control.Applicative.empty' member
-- of the 'Control.Applicative.Alternative' class.

parserZero :: ParsecT s u m a
parserZero
    = ParsecT $ \s _ _ _ eerr ->
      eerr $ unknownError s

unknownError :: State s u -> ParseError
unknownError state        = newErrorUnknown (statePos state)

newErrorUnknown :: SourcePos -> ParseError
newErrorUnknown pos
    = ParseError pos []

从中汲取灵感,似乎唯一合理的解决方法是让我的通用解析器将用户错误类型包装为以下内容:

 data ParserError err = UserError err | UnknownError

 newtype Parser err src target = Parser (ExceptT (ParserError err) (State [src]) target)
  deriving (Functor, Applicative, Alternative, Monad, MonadState [src], MonadError err)

然后empty可以是:

empty = throwError UnknownError

这只是感觉错了,不过。这种包装只是为了满足empty 的要求而存在,它使这个通用解析器的消费者做更多的工作来处理错误(他们现在还必须处理UnknownError 并解开他们的自定义错误)。有什么办法可以避免吗?

【问题讨论】:

  • 也许我没有正确理解这个问题;为什么不简单地要求有一个 Default 实例来表示错误或类似的东西?无需添加包装/展开(除非您想将它与没有默认值的错误类型一起使用,在这种情况下,您只需在使用端进行 both 包装和展开)
  • 我认为需要默认值。决定反对它,因为我不确定我是否有能力制作这样的单子(我想我知道怎么做)。除此之外,我试图避免的所有事情都是在用户端展开(大概我的库需要用自己的错误类型进行包装)。虽然考虑到 Megaparsec 和 Parsec 只是定义了自己的,所以这似乎是要走的路。

标签: parsing haskell applicative alternative-functor


【解决方案1】:

这是标准base 层次结构的问题。它不是非常模块化。在某个完美的世界中,如果我们想实现最大的模块化,Alternative 会被分成三个类型类。

参见 PureScript 世界中的 Alternative 定义:

像这样:

class Functor f <= Alt f where
  alt :: forall a. f a -> f a -> f a  -- alt is (<|>)

class Alt f <= Plus f where
  empty :: forall a. f a

class (Applicative f, Plus f) <= Alternative f

因此,如果类型层次结构足够模块化,您可以为您的 Parser 类型实现 Alt(并具有所有与 &lt;|&gt; 相关的功能),但不是 Alternative。如果AlternativeMonoid 那么AltSemigroup:你可以追加 元素,但你没有 元素。

请注意,之前在 GHC 中的 baseApplicative 不是 Monad 的超类,Semigroup 不在 base 中,并且仅在 GHC-8.4.1 中 Semigroup 将是超类的Monoid。因此,您可以预期,在将来Alternative 会发生类似的事情(模块化)。

【讨论】:

  • 非常有趣的是,在其他世界中做出了更加模块化的选择!感谢您的详细信息!同样有趣的是,代替empty 的定律仅用于关联性和分配性。我想我们只能等待这种模块化随着时间的推移而成熟。作为切线,我很好奇您对将包装器错误类型设置为instance Alternative 的设计选择有何看法。 Parsec 作为参考艺术并没有向我暗示这是一种好方法还是坏方法。也许你有更多的经验?
  • @BaileyParker 好吧,parsec 已经很老了,你应该使用megaparsec(至少作为参考):github.com/mrkkrp/megaparsec#megaparsec-vs-parsec 正如我从megaparsec 来源看到的那样,它还定义了@987654350 @使用特殊错误类型:hackage.haskell.org/package/megaparsec-6.4.0/docs/src/…我认为这是当前类型类层次结构的必要解决方法。
  • 感谢您将我指向 megaparsec!它看起来像很棒的参考艺术。并感谢您的回答:)
猜你喜欢
  • 1970-01-01
  • 2014-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 2013-08-26
  • 2010-10-31
相关资源
最近更新 更多