从阅读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)
显然,我们可以从<|>
获得与many
和some
/ 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} },some
,many
和朋友免费)。
深入研究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
并解开他们的自定义错误)。有什么方法可以避免这种情况吗?
答案 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
,则Alternative
为Monoid
:您可以追加元素,但您没有为空元件。
请注意,之前在GHC Alt
包Semigroup
中的base
不是Applicative
的超类,Monad
不在Semigroup
且仅在GHC中-8.4.1 base
将是Semigroup
的超类。因此,您可以预期,在某些将来Monoid
会发生类似的事情(模块化)。