作为练习¹,我编写了仅使用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 cole,Parser
是一个Alternative
,因此是一个半群。 empty
函数源自Alternative
,而不是Parser
的应用实例。
答案 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。
啊,但是事实并非如此。 Parser是Alternative,是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
等“惰性单子”时。但是解析器单子通常是严格的。