Applicative Parser导出Alternative而不是空

时间:2018-02-25 08:23:39

标签: parsing haskell applicative

从阅读this page开始,我了解在empty中加入Alternative主要是让Alternative成为幺半群(因此更强大)的设计决策。在我看来,这也是因为否则你无法为Alternative表达任何法律。

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

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

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

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的通用方式1}}对于它(当然,我仍然需要实例<|>来保存state parserA错误,但之后我可以{{1} },somemany和朋友免费)。

深入研究Parsec的来源,它似乎颠覆了这一点,因为它不允许自定义错误类型(或者至少optional没有被自定义错误参数化型):

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并解开他们的自定义错误)。有什么方法可以避免这种情况吗?

1 个答案:

答案 0 :(得分:3)

这是标准L1D层次结构的问题。它不是非常模块化的。在一些完美的世界中,如果我们想要实现最大的模块化, for (int i = 0; i <= 3; i++) { for (int j = 0; j <= 3; j++) { if (i == 0) { Console.Write(j); } else { if(j == 0) { Console.Write(i); } else { Console.Write(i * j); } } } Console.Write("\n"); } 将被分成三个类型。

请参阅PureScript world中的base定义:

像这样:

Alternative

因此,如果类型层次结构足够模块化,您可以为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 (并且只包含所有Alt - 相关的函数),而不是Parser。如果<|>Alternative,则AlternativeMonoid:您可以追加元素,但您没有为空元件。

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