【发布时间】: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(当然,我仍然需要实例化<|> 以在parserA 错误上保存state,但随后我可以免费获得some、many、optional 和朋友)。
挖掘 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