Как я могу написать более общую (но эффективную) версию takeWhile1 attoparsec?
Data.Attoparsec.Text
экспорт takeWhile
а также takeWhile1
:
takeWhile :: (Char -> Bool) -> Parser Text
Использовать ввод, пока возвращается предикат
True
и верните потребленный ввод.Этот парсер не подведет. Он вернет пустую строку, если предикат вернет
False
на первый символ ввода.[...]
takeWhile1 :: (Char -> Bool) -> Parser Text
Использовать ввод, пока возвращается предикат
True
и верните потребленный ввод.Этот синтаксический анализатор требует, чтобы предикат преуспел по крайней мере на одном входном символе: он потерпит неудачу, если предикат никогда не вернется
True
или если не осталось ввода.
attoparsec
документация поощряет пользователя
Использовать
Text
-ориентированные парсеры, когда это возможно, напримерtakeWhile1
вместоmany1 anyChar
, Разница в производительности между двумя типами парсера примерно в 100 раз.
Эти два парсера очень полезны, но я продолжаю чувствовать необходимость в более общей версии takeWhile1
точнее, некоторый гипотетический парсер
takeWhileLo :: (Char -> Bool) -> Int -> Parser Text
takeWhileLo f lo = undefined
что бы разобрать хотя бы 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
Это работает как рекламируется,
λ> parseOnly (takeWhileLo (== 'a') 4) "aaa"
Left "not enough input"
λ> parseOnly (takeWhileLo (== 'a') 4) "aaaa"
Right "aaaa"
λ> parseOnly (takeWhileLo (== 'a') 4) "aaaaaaaaaaaaa"
Right "aaaaaaaaaaaaa"
но необходимость упаковки промежуточного списка результатов возвращается count
меня беспокоит, особенно для случаев, когда lo
большой... Кажется, идет вразрез с рекомендацией
использовать
Text
-ориентированные парсеры, когда это возможно [...]
Я что-то пропустил? Есть ли более эффективный / идиоматический способ реализации такого takeWhileLo
комбинатор?
1 ответ
Parser
является монадой, так что вы можете просто проверить возвращаемое значение и потерпеть неудачу, если длина не верна:
takeWhileLo :: (Char -> Bool) -> Int -> Parser Text
takeWhileLo f lo = do
text <- takeWhile f
case T.compareLength text lo of
LT -> empty
_ -> return text
compareLength
от text
пакет. Это эффективнее, чем сравнение text
длина, потому что compareLength
возможно короткое замыкание.