我怎么能写一个更通用(但有效)的attoparsec版本的takeWhile1?

jub*_*0bs 3 haskell parser-combinators attoparsec

Data.Attoparsec.Text出口takeWhiletakeWhile1:

takeWhile :: (Char -> Bool) -> Parser Text
Run Code Online (Sandbox Code Playgroud)

只要谓词返回True就消耗输入,并返回消耗的输入.

此解析器不会失败.如果谓词False在输入的第一个字符上返回,它将返回一个空字符串.

[...]

takeWhile1 :: (Char -> Bool) -> Parser Text
Run Code Online (Sandbox Code Playgroud)

只要谓词返回True就消耗输入,并返回消耗的输入.

此解析器要求谓词在至少一个输入字符上成功:如果谓词永不返回True或者没有输入,则它将失败.

attoparsec的文档鼓励用户

Text尽可能使用面向对象的解析器,例如takeWhile1代替many1 anyChar.两种解析器之间的性能差异大约为100倍.

这两个解析器非常有用,但我一直觉得需要更通用的版本takeWhile1,更具体地说,是一些假设的解析器

takeWhileLo :: (Char -> Bool) -> Int -> Parser Text
takeWhileLo f lo = undefined
Run Code Online (Sandbox Code Playgroud)

这将解析至少 lo满足谓词的字符f,其中lo是任意非负整数.

我看了一下它takeWhile1的实现,但是它使用了一堆私有的函数,Data.Attoparsec.Text.Internal并且似乎不易泛化.

我想出了以下应用实现:

{-# LANGUAGE OverloadedStrings #-}

import           Prelude                  hiding ( takeWhile )

import           Control.Applicative             ( (<*>) )
import           Data.Text                       ( Text )
import qualified Data.Text           as T

import           Data.Attoparsec.Text

takeWhileLo :: (Char -> Bool) -> Int -> Parser Text
takeWhileLo f lo =
  T.append . T.pack <$> count lo (satisfy f) <*> takeWhile f
Run Code Online (Sandbox Code Playgroud)

它像宣传的那样工作,

?> parseOnly (takeWhileLo (== 'a') 4) "aaa"
Left "not enough input"
?> parseOnly (takeWhileLo (== 'a') 4) "aaaa"
Right "aaaa"
?> parseOnly (takeWhileLo (== 'a') 4) "aaaaaaaaaaaaa"
Right "aaaaaaaaaaaaa"
Run Code Online (Sandbox Code Playgroud)

但需要打包返回的中间结果列表count让我担心,特别是对于lo大的情况...这似乎违背了建议

Text尽可能使用面向对象的解析器[...]

我错过了什么吗?是否有更有效/惯用的方式来实现这样的takeWhileLo组合器?

And*_*ács 5

Parser 是一个monad,所以你可以检查返回值,如果长度不正确则会失败:

takeWhileLo :: (Char -> Bool) -> Int -> Parser Text
takeWhileLo f lo = do
  text <- takeWhile f
  case T.compareLength text lo of
    LT -> empty
    _  -> return text
Run Code Online (Sandbox Code Playgroud)

compareLength来自text包裹.它比比较text长度更有效,因为compareLength可能会短路.