Как я могу написать более общую (но эффективную) версию 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 возможно короткое замыкание.

Другие вопросы по тегам