2015-06-30 4 views
3

Data.Attoparsec.Text экспорт takeWhile и takeWhile1:Как я могу написать более общую (но эффективную) версию takeWhile1 attoparsec?

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's implementation, но он использует кучу функций, закрытых для 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?

ответ

5

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 может быть короткозамкнутым.

+0

Я полагаю, что я был слишком сосредоточен на внедрении 'takeWhileLo' в качестве аппликативного комбинатора. Ваше предложение имеет смысл. Благодарю. – Jubobs

+0

Вы имели в виду 'mempty' вместо' empty'? – Jubobs

+1

Это также работает. У нас есть экземпляры «Monoid», «Alternative» и «MonadPlus» для 'Parser' и' mempty', 'empty' и' mzero' все выраженные сбои. –