on master help sign in
Add compareLength and some rewrite rules
a02670dBryan O'Sullivan
Data/Text.hs+53−1
@@ -47,6 +47,7 @@ module Data.Text
47 , init
47 , init
48 , null
48 , null
49 , length
49 , length
50+ , compareLength
50
51
51 -- * Transformations
52 -- * Transformations
52 , map
53 , map
@@ -167,7 +168,7 @@ module Data.Text
167 ) where
168 ) where
168
169
169import Prelude (Char, Bool(..), Functor(..), Int, Maybe(..), String,
170import Prelude (Char, Bool(..), Functor(..), Int, Maybe(..), String,
170− Eq(..), Ord(..), (++),
171+ Eq(..), Ord(..), Ordering(..), (++),
171 Read(..), Show(..),
172 Read(..), Show(..),
172 (&&), (||), (+), (-), (.), ($), ($!), (>>), (*),
173 (&&), (||), (+), (-), (.), ($), ($!), (>>), (*),
173 div, error, not, return, otherwise)
174 div, error, not, return, otherwise)
@@ -414,6 +415,51 @@ length :: Text -> Int
414length t = S.length (stream t)
415length t = S.length (stream t)
415{-# INLINE length #-}
416{-# INLINE length #-}
416
417
418+-- | /O(n)/ Compare the count of characters in a 'Text' to a number.
419+-- Subject to fusion.
420+--
421+-- This function gives the same answer as comparing against the result
422+-- of 'length', but can short circuit if the count of characters is
423+-- greater than the number, and hence be more efficient.
424+compareLength :: Text -> Int -> Ordering
425+compareLength t n = S.compareLengthI (stream t) n
426+{-# INLINE [1] compareLength #-}
427+
428+{-# RULES
429+"TEXT compareN/length -> compareLength" [~1] forall t n.
430+ compare (length t) n = compareLength t n
431+ #-}
432+
433+{-# RULES
434+"TEXT ==N/length -> compareLength/==EQ" [~1] forall t n.
435+ (==) (length t) n = compareLength t n == EQ
436+ #-}
437+
438+{-# RULES
439+"TEXT /=N/length -> compareLength//=EQ" [~1] forall t n.
440+ (/=) (length t) n = compareLength t n /= EQ
441+ #-}
442+
443+{-# RULES
444+"TEXT <N/length -> compareLength/==LT" [~1] forall t n.
445+ (<) (length t) n = compareLength t n == LT
446+ #-}
447+
448+{-# RULES
449+"TEXT <=N/length -> compareLength//=GT" [~1] forall t n.
450+ (<=) (length t) n = compareLength t n /= GT
451+ #-}
452+
453+{-# RULES
454+"TEXT >N/length -> compareLength/==GT" [~1] forall t n.
455+ (>) (length t) n = compareLength t n == GT
456+ #-}
457+
458+{-# RULES
459+"TEXT >=N/length -> compareLength//=LT" [~1] forall t n.
460+ (>=) (length t) n = compareLength t n /= LT
461+ #-}
462+
417-- -----------------------------------------------------------------------------
463-- -----------------------------------------------------------------------------
418-- * Transformations
464-- * Transformations
419-- | /O(n)/ 'map' @f@ @t@ is the 'Text' obtained by applying @f@ to
465-- | /O(n)/ 'map' @f@ @t@ is the 'Text' obtained by applying @f@ to
@@ -461,6 +507,12 @@ replace s d = intercalate d . split s
461-- case conversion rules. As a result, these functions may map one
507-- case conversion rules. As a result, these functions may map one
462-- input character to two or three output characters. For examples,
508-- input character to two or three output characters. For examples,
463-- see the documentation of each function.
509-- see the documentation of each function.
510+--
511+-- /Note/: In some languages, case conversion is a locale- and
512+-- context-dependent operation. The case conversion functions in this
513+-- module are /not/ locale sensitive. Programs that require locale
514+-- sensitivity should use appropriate versions of the case mapping
515+-- functions from the @text-icu@ package.
464
516
465-- | /O(n)/ Convert a string to folded case. This function is mainly
517-- | /O(n)/ Convert a string to folded case. This function is mainly
466-- useful for performing caseless (also known as case insensitive)
518-- useful for performing caseless (also known as case insensitive)
Data/Text/Fusion/Common.hs+22−2
@@ -29,6 +29,7 @@ module Data.Text.Fusion.Common
29 , init
29 , init
30 , null
30 , null
31 , lengthI
31 , lengthI
32+ , compareLengthI
32 , isSingleton
33 , isSingleton
33
34
34 -- * Transformations
35 -- * Transformations
@@ -99,8 +100,8 @@ module Data.Text.Fusion.Common
99 ) where
100 ) where
100
101
101import Prelude (Bool(..), Char, Eq(..), Int, Integral, Maybe(..),
102import Prelude (Bool(..), Char, Eq(..), Int, Integral, Maybe(..),
102− Ord(..), String, (.), ($), (+), (-), (*), (++), (&&),
103+ Ord(..), Ordering(..), String, (.), ($), (+), (-), (*), (++),
103− fromIntegral, otherwise)
104+ (&&), fromIntegral, otherwise)
104import qualified Data.List as L
105import qualified Data.List as L
105import qualified Prelude as P
106import qualified Prelude as P
106import Data.Int (Int64)
107import Data.Int (Int64)
@@ -266,6 +267,25 @@ lengthI (Stream next s0 _len) = loop_length 0 s0
266 Yield _ s' -> loop_length (z + 1) s'
267 Yield _ s' -> loop_length (z + 1) s'
267{-# INLINE[0] lengthI #-}
268{-# INLINE[0] lengthI #-}
268
269
270+-- | /O(n)/ Compares the count of characters in a string to a number.
271+-- Subject to fusion.
272+--
273+-- This function gives the same answer as comparing against the result
274+-- of 'lengthI', but can short circuit if the count of characters is
275+-- greater than the number, and hence be more efficient.
276+compareLengthI :: Integral a => Stream Char -> a -> Ordering
277+compareLengthI (Stream next s0 len) n =
278+ case exactly len of
279+ Nothing -> loop_cmp 0 s0
280+ Just i -> compare (fromIntegral i) n
281+ where
282+ loop_cmp !z s = case next s of
283+ Done -> compare z n
284+ Skip s' -> loop_cmp z s'
285+ Yield _ s' | z > n -> GT
286+ | otherwise -> loop_cmp (z + 1) s'
287+{-# INLINE[0] compareLengthI #-}
288+
269-- | /O(n)/ Indicate whether a string contains exactly one element.
289-- | /O(n)/ Indicate whether a string contains exactly one element.
270isSingleton :: Stream Char -> Bool
290isSingleton :: Stream Char -> Bool
271isSingleton (Stream next s0 _len) = loop 0 s0
291isSingleton (Stream next s0 _len) = loop 0 s0
Data/Text/Fusion/Size.hs+6
@@ -16,6 +16,7 @@
16module Data.Text.Fusion.Size
16module Data.Text.Fusion.Size
17 (
17 (
18 Size
18 Size
19+ , exactly
19 , exactSize
20 , exactSize
20 , maxSize
21 , maxSize
21 , unknownSize
22 , unknownSize
@@ -36,6 +37,11 @@ data Size = Exact {-# UNPACK #-} !Int -- ^ Exact size.
36 | Unknown -- ^ Unknown size.
37 | Unknown -- ^ Unknown size.
37 deriving (Eq, Show)
38 deriving (Eq, Show)
38
39
40+exactly :: Size -> Maybe Int
41+exactly (Exact n) = Just n
42+exactly _ = Nothing
43+{-# INLINE exactly #-}
44+
39exactSize :: Int -> Size
45exactSize :: Int -> Size
40exactSize n =
46exactSize n =
41#if defined(ASSERTS)
47#if defined(ASSERTS)
Data/Text/Lazy.hs+16−1
@@ -55,6 +55,7 @@ module Data.Text.Lazy
55 , init
55 , init
56 , null
56 , null
57 , length
57 , length
58+ , compareLength
58
59
59 -- * Transformations
60 -- * Transformations
60 , map
61 , map
@@ -173,7 +174,7 @@ module Data.Text.Lazy
173 ) where
174 ) where
174
175
175import Prelude (Char, Bool(..), Maybe(..), String,
176import Prelude (Char, Bool(..), Maybe(..), String,
176− Eq(..), Ord(..), Read(..), Show(..),
177+ Eq(..), Ord(..), Ordering, Read(..), Show(..),
177 (&&), (+), (-), (.), ($), (++),
178 (&&), (+), (-), (.), ($), (++),
178 div, error, flip, fromIntegral, not, otherwise)
179 div, error, flip, fromIntegral, not, otherwise)
179import qualified Prelude as P
180import qualified Prelude as P
@@ -422,6 +423,20 @@ length = foldlChunks go 0
422 S.length (stream t) = length t
423 S.length (stream t) = length t
423 #-}
424 #-}
424
425
426+-- | /O(n)/ Compare the count of characters in a 'Text' to a number.
427+-- Subject to fusion.
428+--
429+-- This function gives the same answer as comparing against the result
430+-- of 'length', but can short circuit if the count of characters is
431+-- greater than the number, and hence be more efficient.
432+compareLength :: Text -> Int64 -> Ordering
433+compareLength t n = S.compareLengthI (stream t) n
434+{-# INLINE [1] compareLength #-}
435+
436+-- We don't apply those otherwise appealing length-to-compareLength
437+-- rewrite rules here, because they can change the strictness
438+-- properties of code.
439+
425-- | /O(n)/ 'map' @f@ @t@ is the 'Text' obtained by applying @f@ to
440-- | /O(n)/ 'map' @f@ @t@ is the 'Text' obtained by applying @f@ to
426-- each element of @t@. Subject to array fusion.
441-- each element of @t@. Subject to array fusion.
427map :: (Char -> Char) -> Text -> Text
442map :: (Char -> Char) -> Text -> Text
tests/Properties.hs+5−1
@@ -200,6 +200,8 @@ s_length = length `eqP` S.length
200sf_length p = (length . L.filter p) `eqP` (S.length . S.filter p)
200sf_length p = (length . L.filter p) `eqP` (S.length . S.filter p)
201t_length = length `eqP` T.length
201t_length = length `eqP` T.length
202tl_length = L.genericLength `eqP` TL.length
202tl_length = L.genericLength `eqP` TL.length
203+t_compareLength t = (compare (T.length t)) `eq` T.compareLength t
204+tl_compareLength t= (compare (TL.length t)) `eq` TL.compareLength t
203
205
204s_map f = map f `eqP` (unpackS . S.map f)
206s_map f = map f `eqP` (unpackS . S.map f)
205s_map_s f = map f `eqP` (unpackS . S.unstream . S.map f)
207s_map_s f = map f `eqP` (unpackS . S.unstream . S.map f)
@@ -676,7 +678,9 @@ tests = [
676 testProperty "s_length" s_length,
678 testProperty "s_length" s_length,
677 testProperty "sf_length" sf_length,
679 testProperty "sf_length" sf_length,
678 testProperty "t_length" t_length,
680 testProperty "t_length" t_length,
679− testProperty "tl_length" tl_length
681+ testProperty "tl_length" tl_length,
682+ testProperty "t_compareLength" t_compareLength,
683+ testProperty "tl_compareLength" tl_compareLength
680 ],
684 ],
681
685
682 testGroup "transformations" [
686 testGroup "transformations" [