身份解析器

时间:2019-07-15 14:44:19

标签: haskell parser-combinators trifecta

作为练习¹,我编写了仅使用char解析器和Trifecta的字符串解析器:

import           Text.Trifecta
import           Control.Applicative            ( pure )

stringParserWithChar :: String -> Parser Char
stringParserWithChar stringToParse =
  foldr (\c otherParser -> otherParser >> char c) identityParser
    $ reverse stringToParse
  where identityParser = pure '?' -- ← This works but I think I can do better

解析器可以正常工作:

parseString (stringParserWithChar "123") mempty "1234"
-- Yields: Success '3'

但是,我对应用identityParser的特定foldr感到不满意。必须为pure选择一个任意字符似乎很不客气。

我的第一个直觉是使用mempty,但Parser不是一个等分线。它是适用的,但empty构成了不成功的解析器²。

我正在寻找的是一种解析器,当与其他解析器结合使用时,该解析器可用作中立元素。它应该什么都不做,即不要前进光标,而让下一个解析器使用该字符。

在Trifecta或另一个库中是否有如上所述的身份解析器?还是不打算在fold中使用解析器?


¹练习来自Haskell Programming from first principles书的“解析器组合器”一章。

²很有帮助的pointed out by coleParser是一个Alternative,因此是一个半群。 empty函数源自Alternative,而不是Parser的应用实例。

2 个答案:

答案 0 :(得分:2)

您不希望它解析String吗?现在,从函数签名可以看出,它解析Char,并返回最后一个字符。仅仅因为您只有一个Char解析器并不意味着您不能创建一个String解析器。

我将假设您要解析一个字符串,在这种情况下,您的基本情况很简单:您的identityParser只是pure ""

我认为类似这样的方法应该起作用(并且应该的顺序正确,但是可能相反)。

stringParserWithChar :: String -> Parser String
stringParserWithChar = traverse char

展开,你得到类似的东西

stringParserWithChar' :: String -> Parser String
stringParserWithChar' "" = pure ""
stringParserWithChar' (c:cs) = liftA2 (:) (char c) (stringParserWithChar' cs)
-- the above with do notation, note that you can also just sequence the results of
-- 'char c' and 'stringParserWithChar' cs' and instead just return 'pure (c:cs)'
-- stringParserWithChar' (c:cs) = do
--   c'  <- char c
--   cs' <- stringParserWithChar' cs
--   pure (c':cs')

让我知道它们是否不起作用,因为我现在无法对其进行测试...

关于类人动物的题外话

  

我的第一个直觉是使用mempty,但Parser不是一个monoid。

啊,但是事实并非如此。 ParserAlternative,是Monoid。但是,您实际上不需要查看Alt的{​​{1}}类型类就可以了解这一点; Data.Monoid的类型定义看起来像Alternative的:

Monoid
class Applicative f => Alternative f where
  empty :: f a
  (<|>) :: f a -> f a -> f a
  -- more definitions...

不幸的是,您想要的是行为更像产品而不是class Semigroup a => Monoid a where mempty :: a mappend :: a -> a -> a -- more definitions... 的产品,但这就是Alt的默认行为。

答案 1 :(得分:1)

让我们将fold + reverse改写为fold,以阐明正在发生的事情:

var myKeys = orderedDictionary.Keys.Cast<string>().ToArray();

每当您看到stringParserWithChar :: String -> Parser Char stringParserWithChar = foldl (\otherParser c -> otherParser >> char c) identityParser where identityParser = pure '?' 用于使用其foldl实例来构建某些内容时,就会有些可疑[*]。它暗示您确实想要某种 monadic 折叠。让我们看看这里...

Monad

这将遇到与您之前看到的相同的麻烦:您可以将什么用作初始值?因此,让我们使用标准技巧,以import Control.Monad -- foldM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b attempt1 :: String -> Parser Char attempt1 = foldM _f _acc 开始:

Maybe

现在,我们可以从-- (Control.Monad.<=<) -- :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c stringParserWithChar :: String -> Parser Char stringParserWithChar = maybe empty pure <=< foldM _f _acc 开始折叠,然后立即切换到Nothing并停留在那里。我让你填补空白。 GHC将帮助您显示其类型。

[*]主要例外是当它是Just,懒人Reader,懒人Writer等“惰性单子”时。但是解析器单子通常是严格的。