From efe048798a01daaee814e040578c7b4d0c67795f Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Wed, 23 Sep 2026 13:00:00 +0200 Subject: [PATCH 01/17] Define `splitNE` and `splitOnNE` that return `NonEmpty` for both lazy and strict modules --- src/Data/Text.hs | 98 ++++++++++++++++++++++++++++++++------ src/Data/Text/Lazy.hs | 108 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 173 insertions(+), 33 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 8160aa9b..5794d25b 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -2,6 +2,7 @@ {-# LANGUAGE TemplateHaskellQuotes #-} {-# LANGUAGE Trustworthy #-} {-# LANGUAGE UnliftedFFITypes #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE PatternSynonyms #-} @@ -172,7 +173,9 @@ module Data.Text -- ** Breaking into many substrings -- $split , splitOn + , splitOnNE , split + , splitNE , chunksOf -- ** Breaking into lines and words @@ -274,7 +277,7 @@ import qualified Data.Text.Lazy as L #endif import Data.Word (Word8) import Foreign.C.Types -import GHC.Base (eqChar, neChar, eqInt, neInt, gtInt, geInt, ltInt, leInt) +import GHC.Base (eqChar, neChar, eqInt, neInt, gtInt, geInt, ltInt, leInt, NonEmpty ((:|))) import qualified GHC.Exts as Exts import GHC.Int (Int8) import GHC.Stack (HasCallStack) @@ -1791,6 +1794,8 @@ tailsNE t -- -- In (unlikely) bad cases, this function's time complexity degrades -- towards /O(n*m)/. +-- +-- See also 'splitOnNE' for a version of this function returning 'NonEmpty Text'. splitOn :: HasCallStack => Text -- ^ String to split on. If this string is empty, an error @@ -1798,13 +1803,7 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -splitOn pat@(Text _ _ l) src@(Text arr off len) - | l <= 0 = emptyError "splitOn" - | isSingleton pat = split (== unsafeHead pat) src - | otherwise = go 0 (indices pat src) - where - go !s (x:xs) = text arr (s+off) (x-s) : go (x+l) xs - go s _ = [text arr (s+off) (len-s)] +splitOn pat = NonEmptyList.toList . splitOnNE pat {-# INLINE [1] splitOn #-} {-# RULES @@ -1812,6 +1811,51 @@ splitOn pat@(Text _ _ l) src@(Text arr off len) splitOn (singleton c) t = split (==c) t #-} +-- | /O(m+n)/ Break a 'Text' into pieces separated by the first 'Text' +-- argument (which cannot be empty), consuming the delimiter. An empty +-- delimiter is invalid, and will cause an error to be raised. +-- +-- Examples: +-- +-- >>> splitOnNE "\r\n" "a\r\nb\r\nd\r\ne" +-- "a" :| ["b","d","e"] +-- +-- >>> splitOnNE "aaa" "aaaXaaaXaaaXaaa" +-- "" :| ["X","X","X",""] +-- +-- >>> splitOnNE "x" "x" +-- "" :| [""] +-- +-- and +-- +-- > intercalate s . splitOnNE s == id +-- > splitOnNE (singleton c) == splitNE (==c) +-- +-- (Note: the string @s@ to split on above cannot be empty.) +-- +-- In (unlikely) bad cases, this function's time complexity degrades +-- towards /O(n*m)/. +splitOnNE :: HasCallStack + => Text + -- ^ String to split on. If this string is empty, an error + -- will occur. + -> Text + -- ^ Input text. + -> NonEmptyList.NonEmpty Text +splitOnNE pat@(Text _ _ l) src@(Text arr off len) + | null pat = emptyError "splitOnNE" + | isSingleton pat = splitNE (== unsafeHead pat) src + | otherwise = NonEmptyList.fromList $ go 0 (indices pat src) + where + go !s (x:xs) = text arr (s+off) (x-s) : go (x+l) xs + go s _ = [text arr (s+off) (len-s)] +{-# INLINE [1] splitOnNE #-} + +{-# RULES +"TEXT splitOnNE/singleton -> split/==" [~1] forall c t. + splitOnNE (singleton c) t = splitNE (==c) t + #-} + -- | /O(n)/ Splits a 'Text' into components delimited by separators, -- where the predicate returns True for a separator element. The -- resulting components do not contain the separators. Two adjacent @@ -1822,15 +1866,41 @@ splitOn pat@(Text _ _ l) src@(Text arr off len) -- -- >>> split (=='a') "" -- [""] +-- +-- See also 'splitNE' for a version of this function returning 'NonEmpty Text'. split :: (Char -> Bool) -> Text -> [Text] -split p t - | null t = [empty] - | otherwise = loop t - where loop s | null s' = [l] - | otherwise = l : loop (unsafeTail s') - where (# l, s' #) = span_ (not . p) s +split p = NonEmptyList.toList . splitNE p {-# INLINE split #-} +-- | /O(n)/ Splits a 'Text' into components delimited by separators, +-- where the predicate returns True for a separator element. The +-- resulting components do not contain the separators. Two adjacent +-- separators result in an empty component in the output. eg. +-- +-- >>> splitNE (=='a') "aabbaca" +-- "" :| ["","bb","c",""] +-- +-- >>> splitNE (=='a') "" +-- "" :| [] +-- +-- >>> splitNE (=='b') "aabbaca" +-- "aa" :| ["","aca"] +-- +splitNE :: (Char -> Bool) -> Text -> NonEmptyList.NonEmpty Text +splitNE p t +-- XXX: Or maybe the best is to use the original implementation +-- and stick a `NonEmpty.fromList` at the beginning? + | null t = NonEmptyList.singleton empty + | otherwise = let (# l, r #) = span_ (not . p) t + in l :| loop r + where + loop :: Text -> [Text] + loop "" = [] + loop s | null s' = [l'] + | otherwise = l' : loop s' + where (# l', s' #) = span_ (not . p) (unsafeTail s) +{-# INLINE splitNE #-} + -- | /O(n)/ Splits a 'Text' into components of length @k@. The last -- element may be shorter than the other chunks, depending on the -- length of the input. Examples: diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index d590dc44..614eef50 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -172,7 +172,9 @@ module Data.Text.Lazy -- ** Breaking into many substrings -- $split , splitOn + , splitOnNE , split + , splitNE , chunksOf -- , breakSubstring @@ -1599,9 +1601,14 @@ tailsNE ts@(Chunk t ts') -- -- Examples: -- --- > splitOn "\r\n" "a\r\nb\r\nd\r\ne" == ["a","b","d","e"] --- > splitOn "aaa" "aaaXaaaXaaaXaaa" == ["","X","X","X",""] --- > splitOn "x" "x" == ["",""] +-- >>> Data.Text.Lazy.splitOn "\r\n" "a\r\nb\r\nd\r\ne" +-- ["a","b","d","e"] +-- +-- >>> Data.Text.Lazy.splitOn "aaa" "aaaXaaaXaaaXaaa" +-- ["","X","X","X",""] +-- +-- >>> Data.Text.Lazy.splitOn "x" "x" +-- ["",""] -- -- and -- @@ -1622,20 +1629,64 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -splitOn pat src - | null pat = emptyError "splitOn" - | isSingleton pat = split (== head pat) src +splitOn pat = NE.toList . splitOnNE pat +{-# INLINE [1] splitOn #-} + +{-# RULES +"LAZY TEXT splitOn/singleton -> split/==" [~1] forall c t. + splitOn (singleton c) t = split (==c) t + #-} + +-- | /O(m+n)/ Break a 'Text' into pieces separated by the first 'Text' +-- argument (which cannot be an empty string), consuming the +-- delimiter. An empty delimiter is invalid, and will cause an error +-- to be raised. +-- +-- Examples: +-- +-- >>> Data.Text.Lazy.splitOnNE "\r\n" "a\r\nb\r\nd\r\ne" +-- "a" :| ["b","d","e"] +-- +-- >>> Data.Text.Lazy.splitOnNE "aaa" "aaaXaaaXaaaXaaa" +-- "" :| ["X","X","X",""] +-- +-- >>> Data.Text.Lazy.splitOnNE "x" "x" +-- "" :| [""] +-- +-- +-- and +-- +-- > intercalate s . splitOnNE s == id +-- > splitOnNE (singleton c) == splitNE (==c) +-- +-- (Note: the string @s@ to split on above cannot be empty.) +-- +-- This function is strict in its first argument, and lazy in its +-- second. +-- +-- In (unlikely) bad cases, this function's time complexity degrades +-- towards /O(n*m)/. +splitOnNE :: HasCallStack + => Text + -- ^ String to split on. If this string is empty, an error + -- will occur. + -> Text + -- ^ Input text. + -> NE.NonEmpty Text +splitOnNE pat src + | null pat = emptyError "splitOnNE" + | isSingleton pat = splitNE (== head pat) src | otherwise = go 0 (indices pat src) src where - go _ [] cs = [cs] + go _ [] cs = NE.singleton cs go !i (x:xs) cs = let h :*: t = splitAtWord (x-i) cs - in h : go (x+l) xs (dropWords l t) + in h :| NE.toList (go (x+l) xs (dropWords l t)) l = foldlChunks (\a (T.Text _ _ b) -> a + intToInt64 b) 0 pat -{-# INLINE [1] splitOn #-} +{-# INLINE [1] splitOnNE #-} {-# RULES -"LAZY TEXT splitOn/singleton -> split/==" [~1] forall c t. - splitOn (singleton c) t = split (==c) t +"LAZY TEXT splitOnNE/singleton -> split/==" [~1] forall c t. + splitOnNE (singleton c) t = splitNE (==c) t #-} -- | /O(n)/ Splits a 'Text' into components delimited by separators, @@ -1643,17 +1694,36 @@ splitOn pat src -- resulting components do not contain the separators. Two adjacent -- separators result in an empty component in the output. eg. -- --- > split (=='a') "aabbaca" == ["","","bb","c",""] --- > split (=='a') [] == [""] +-- >>> Data.Text.Lazy.split (=='a') "aabbaca" +-- ["","","bb","c",""] +-- +-- >>> Data.Text.Lazy.split (=='a') "" +-- [""] +-- split :: (Char -> Bool) -> Text -> [Text] -split _ Empty = [Empty] -split p (Chunk t0 ts0) = comb [] (T.split p t0) ts0 - where comb acc (s:[]) Empty = revChunks (s:acc) : [] - comb acc (s:[]) (Chunk t ts) = comb (s:acc) (T.split p t) ts - comb acc (s:ss) ts = revChunks (s:acc) : comb [] ss ts - comb _ [] _ = impossibleError "split" +split p = NE.toList . splitNE p {-# INLINE split #-} +-- | /O(n)/ Splits a 'Text' into components delimited by separators, +-- where the predicate returns True for a separator element. The +-- resulting components do not contain the separators. Two adjacent +-- separators result in an empty component in the output. eg. +-- +-- >>> Data.Text.Lazy.splitNE (=='a') "aabbaca" +-- "" :| ["","bb","c",""] +-- +-- >>> Data.Text.Lazy.splitNE (=='a') "" +-- "" :| [] +-- +splitNE :: (Char -> Bool) -> Text -> NE.NonEmpty Text +splitNE _ Empty = NE.singleton Empty +splitNE p (Chunk t0 ts0) = comb [] (T.splitNE p t0) ts0 + where comb :: [T.Text] -> NE.NonEmpty T.Text -> Text -> NE.NonEmpty Text + comb acc (s :| []) Empty = revChunks (s:acc) :| [] + comb acc (s :| []) (Chunk t ts) = comb (s:acc) (T.splitNE p t) ts + comb acc (s :| ss : sss) ts = revChunks (s:acc) :| NE.toList (comb [] (ss :| sss) ts) +{-# INLINE splitNE #-} + -- | /O(n)/ Splits a 'Text' into components of length @k@. The last -- element may be shorter than the other chunks, depending on the -- length of the input. Examples: From ccfc93bd7de9a29d9762bd8d5d306176fd451a76 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Wed, 23 Sep 2026 12:55:33 +0200 Subject: [PATCH 02/17] Add some examples of corner cases + improve doc --- src/Data/Text.hs | 30 ++++++++++++++++++++++++++++++ src/Data/Text/Internal/Lazy.hs | 3 ++- src/Data/Text/Lazy.hs | 11 +++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 5794d25b..607fcad8 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1066,6 +1066,12 @@ center k c t -- -- >>> transpose ["blue","red"] -- ["br","le","ud","e"] +-- +-- >>> transpose [""] +-- [] +-- +-- >>> transpose [] +-- [] transpose :: [Text] -> [Text] transpose ts = P.map pack (L.transpose (P.map unpack ts)) @@ -1711,6 +1717,21 @@ spanEndM p t@(Text arr off len) = go (len-1) {-# INLINE spanEndM #-} -- | /O(n)/ Group characters in a string according to a predicate. +-- +-- >>> groupBy (\a b -> a < b) "7890012" +-- ["789","0","012"] +-- +-- >>> groupBy (\_ _ -> True) "hello" +-- ["hello"] +-- +-- >>> groupBy (\_ _ -> False) "hello" +-- ["h","e","l","l","o"] +-- +-- >>> groupBy (P.error "not called") "" +-- [] +-- +-- >>> groupBy (P.error "not called") "" +-- [] groupBy :: (Char -> Char -> Bool) -> Text -> [Text] groupBy p = loop where @@ -1735,6 +1756,9 @@ group = groupBy (==) -- | /O(n)/ Return all initial segments of the given 'Text', shortest -- first. +-- +-- >>> inits "" +-- [""] inits :: Text -> [Text] inits = (NonEmptyList.toList $!) . initsNE @@ -1752,6 +1776,9 @@ initsNE t = empty NonEmptyList.:| case t of -- | /O(n)/ Return all final segments of the given 'Text', longest -- first. +-- +-- >>> tails "" +-- [""] tails :: Text -> [Text] tails = (NonEmptyList.toList $!) . tailsNE @@ -1905,6 +1932,9 @@ splitNE p t -- element may be shorter than the other chunks, depending on the -- length of the input. Examples: -- +-- >>> chunksOf 3 "" +-- [] +-- -- >>> chunksOf 3 "foobarbaz" -- ["foo","bar","baz"] -- diff --git a/src/Data/Text/Internal/Lazy.hs b/src/Data/Text/Internal/Lazy.hs index 9cf22793..b3384070 100644 --- a/src/Data/Text/Internal/Lazy.hs +++ b/src/Data/Text/Internal/Lazy.hs @@ -53,7 +53,8 @@ data Text = Empty -- -- @since 2.1.2 | Chunk {-# UNPACK #-} !T.Text Text - -- ^ Chunks must be non-empty, this invariant is not checked. + -- ^ The @!T.Text@ field must be non-empty; this invariant is not + -- checked. See also 'chunk'. -- | Type synonym for the lazy flavour of 'Text'. -- diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index 614eef50..7dba0898 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -1998,6 +1998,17 @@ zipWith f t1 t2 = unstream (S.zipWith g (stream t1) (stream t2)) show :: Show a => a -> Text show = pack . P.show +-- >>> revChunks ["one", "two"] +-- "twoone" +-- +-- >>> revChunks ["one"] +-- "one" +-- +-- >>> revChunks [""] +-- "" +-- +-- >>> revChunks [] +-- "" revChunks :: [T.Text] -> Text revChunks = L.foldl' (flip chunk) Empty From 803618a6d9a771bf6116eb17eea364399ba42e6a Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Thu, 24 Sep 2026 09:35:28 +0200 Subject: [PATCH 03/17] Take into account that `NE.singleton` did not exist before base 4.15 --- src/Data/Text.hs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 607fcad8..7424fe42 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1917,7 +1917,7 @@ splitNE :: (Char -> Bool) -> Text -> NonEmptyList.NonEmpty Text splitNE p t -- XXX: Or maybe the best is to use the original implementation -- and stick a `NonEmpty.fromList` at the beginning? - | null t = NonEmptyList.singleton empty + | null t = singletonNE empty | otherwise = let (# l, r #) = span_ (not . p) t in l :| loop r where @@ -1926,6 +1926,12 @@ splitNE p t loop s | null s' = [l'] | otherwise = l' : loop s' where (# l', s' #) = span_ (not . p) (unsafeTail s) + singletonNE :: a -> NonEmptyList.NonEmpty a +#if MIN_VERSION_base(4,15,0) + singletonNE = NonEmptyList.singleton +#else + singletonNE = (:| []) +#endif {-# INLINE splitNE #-} -- | /O(n)/ Splits a 'Text' into components of length @k@. The last From daba41a7e020beea0c9c0e241b9ab9dc73559e64 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Thu, 24 Sep 2026 10:04:39 +0200 Subject: [PATCH 04/17] What is going on with tests? --- src/Data/Text.hs | 4 +++- src/Data/Text/Lazy.hs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 7424fe42..787685c5 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1830,7 +1830,9 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -splitOn pat = NonEmptyList.toList . splitOnNE pat +splitOn pat src + | null pat = emptyError "splitOn" -- XXX Why if I comment this tests fail? + | otherwise = NonEmptyList.toList $ splitOnNE pat src {-# INLINE [1] splitOn #-} {-# RULES diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index 7dba0898..3d882b20 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -1629,7 +1629,9 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -splitOn pat = NE.toList . splitOnNE pat +splitOn pat src + | null pat = emptyError "splitOn" -- XXX Why if I comment this tests fail? + | otherwise = NE.toList $ splitOnNE pat src {-# INLINE [1] splitOn #-} {-# RULES From 9b519ed0d4fd09482edf6ae95635cb8a417b1eb1 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Mon, 28 Sep 2026 11:03:53 +0200 Subject: [PATCH 05/17] fixup! Take into account that `NE.singleton` did not exist before base 4.15 --- src/Data/Text/Lazy.hs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index 3d882b20..469a8552 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -1680,10 +1680,15 @@ splitOnNE pat src | isSingleton pat = splitNE (== head pat) src | otherwise = go 0 (indices pat src) src where - go _ [] cs = NE.singleton cs + go _ [] cs = singletonNE cs go !i (x:xs) cs = let h :*: t = splitAtWord (x-i) cs in h :| NE.toList (go (x+l) xs (dropWords l t)) l = foldlChunks (\a (T.Text _ _ b) -> a + intToInt64 b) 0 pat +#if MIN_VERSION_base(4,15,0) + singletonNE = NE.singleton +#else + singletonNE = (:| []) +#endif {-# INLINE [1] splitOnNE #-} {-# RULES From da4a3e7a0a28444271b461e226f016e6b47256fc Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Mon, 28 Sep 2026 11:34:22 +0200 Subject: [PATCH 06/17] Address feedback: don't call `toList` in loops --- src/Data/Text.hs | 2 -- src/Data/Text/Lazy.hs | 19 +++++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 787685c5..a5717984 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1917,8 +1917,6 @@ split p = NonEmptyList.toList . splitNE p -- splitNE :: (Char -> Bool) -> Text -> NonEmptyList.NonEmpty Text splitNE p t --- XXX: Or maybe the best is to use the original implementation --- and stick a `NonEmpty.fromList` at the beginning? | null t = singletonNE empty | otherwise = let (# l, r #) = span_ (not . p) t in l :| loop r diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index 469a8552..2004811d 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -1678,17 +1678,12 @@ splitOnNE :: HasCallStack splitOnNE pat src | null pat = emptyError "splitOnNE" | isSingleton pat = splitNE (== head pat) src - | otherwise = go 0 (indices pat src) src + | otherwise = NE.fromList $ go 0 (indices pat src) src where - go _ [] cs = singletonNE cs + go _ [] cs = [cs] go !i (x:xs) cs = let h :*: t = splitAtWord (x-i) cs - in h :| NE.toList (go (x+l) xs (dropWords l t)) + in h : (go (x+l) xs (dropWords l t)) l = foldlChunks (\a (T.Text _ _ b) -> a + intToInt64 b) 0 pat -#if MIN_VERSION_base(4,15,0) - singletonNE = NE.singleton -#else - singletonNE = (:| []) -#endif {-# INLINE [1] splitOnNE #-} {-# RULES @@ -1724,11 +1719,11 @@ split p = NE.toList . splitNE p -- splitNE :: (Char -> Bool) -> Text -> NE.NonEmpty Text splitNE _ Empty = NE.singleton Empty -splitNE p (Chunk t0 ts0) = comb [] (T.splitNE p t0) ts0 - where comb :: [T.Text] -> NE.NonEmpty T.Text -> Text -> NE.NonEmpty Text - comb acc (s :| []) Empty = revChunks (s:acc) :| [] +splitNE p (Chunk t0 ts0) = NE.fromList $ comb [] (T.splitNE p t0) ts0 + where comb :: [T.Text] -> NE.NonEmpty T.Text -> Text -> [Text] + comb acc (s :| []) Empty = revChunks (s:acc) : [] comb acc (s :| []) (Chunk t ts) = comb (s:acc) (T.splitNE p t) ts - comb acc (s :| ss : sss) ts = revChunks (s:acc) :| NE.toList (comb [] (ss :| sss) ts) + comb acc (s :| ss : sss) ts = revChunks (s:acc) : comb [] (ss :| sss) ts {-# INLINE splitNE #-} -- | /O(n)/ Splits a 'Text' into components of length @k@. The last From cd4272d2797edb116233d9f4d2681528892c6fae Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Mon, 28 Sep 2026 12:18:13 +0200 Subject: [PATCH 07/17] Take into account that `toList` was lazy before base 4.22 --- src/Data/Text.hs | 7 ++++--- src/Data/Text/Lazy.hs | 8 +++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index a5717984..8693acfc 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1830,9 +1830,10 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -splitOn pat src - | null pat = emptyError "splitOn" -- XXX Why if I comment this tests fail? - | otherwise = NonEmptyList.toList $ splitOnNE pat src +#if MIN_VERSION_base(4,22,0) +splitOn "" = emptyError "splitOn" +#endif +splitOn pat = NonEmptyList.toList . splitOnNE pat {-# INLINE [1] splitOn #-} {-# RULES diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index 2004811d..b2471d10 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -5,6 +5,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Data.Text.Lazy @@ -1629,9 +1630,10 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -splitOn pat src - | null pat = emptyError "splitOn" -- XXX Why if I comment this tests fail? - | otherwise = NE.toList $ splitOnNE pat src +#if MIN_VERSION_base(4,22,0) +splitOn "" = emptyError "splitOn" +#endif +splitOn pat = NE.toList . splitOnNE pat {-# INLINE [1] splitOn #-} {-# RULES From bc01925d959ff9e210f235406a323cd6d7eee3ef Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Tue, 29 Sep 2026 08:29:28 +0200 Subject: [PATCH 08/17] Avoid CPP pre-processing directives --- src/Data/Text.hs | 10 +--------- src/Data/Text/Lazy.hs | 2 -- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 8693acfc..f5b64354 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1830,9 +1830,7 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -#if MIN_VERSION_base(4,22,0) splitOn "" = emptyError "splitOn" -#endif splitOn pat = NonEmptyList.toList . splitOnNE pat {-# INLINE [1] splitOn #-} @@ -1918,7 +1916,7 @@ split p = NonEmptyList.toList . splitNE p -- splitNE :: (Char -> Bool) -> Text -> NonEmptyList.NonEmpty Text splitNE p t - | null t = singletonNE empty + | null t = empty :| [] | otherwise = let (# l, r #) = span_ (not . p) t in l :| loop r where @@ -1927,12 +1925,6 @@ splitNE p t loop s | null s' = [l'] | otherwise = l' : loop s' where (# l', s' #) = span_ (not . p) (unsafeTail s) - singletonNE :: a -> NonEmptyList.NonEmpty a -#if MIN_VERSION_base(4,15,0) - singletonNE = NonEmptyList.singleton -#else - singletonNE = (:| []) -#endif {-# INLINE splitNE #-} -- | /O(n)/ Splits a 'Text' into components of length @k@. The last diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index b2471d10..f45f4632 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -1630,9 +1630,7 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -#if MIN_VERSION_base(4,22,0) splitOn "" = emptyError "splitOn" -#endif splitOn pat = NE.toList . splitOnNE pat {-# INLINE [1] splitOn #-} From 74fb4ebae435cc36d1817a35abbf945554f86596 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Tue, 29 Sep 2026 09:26:05 +0200 Subject: [PATCH 09/17] Avoid `OverloadedStrings` extension --- src/Data/Text.hs | 6 +++--- src/Data/Text/Lazy.hs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index f5b64354..0e666210 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -2,7 +2,6 @@ {-# LANGUAGE TemplateHaskellQuotes #-} {-# LANGUAGE Trustworthy #-} {-# LANGUAGE UnliftedFFITypes #-} -{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE PatternSynonyms #-} @@ -1830,8 +1829,9 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -splitOn "" = emptyError "splitOn" -splitOn pat = NonEmptyList.toList . splitOnNE pat +splitOn pat + | null pat = emptyError "splitOn" + | otherwise = NonEmptyList.toList . splitOnNE pat {-# INLINE [1] splitOn #-} {-# RULES diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index f45f4632..4fb55e2c 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -5,7 +5,6 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Data.Text.Lazy @@ -1630,8 +1629,9 @@ splitOn :: HasCallStack -> Text -- ^ Input text. -> [Text] -splitOn "" = emptyError "splitOn" -splitOn pat = NE.toList . splitOnNE pat +splitOn pat + | null pat = emptyError "splitOn" + | otherwise = NE.toList . splitOnNE pat {-# INLINE [1] splitOn #-} {-# RULES From 444c5bd15bfcd32d22c879461f42e6e915db0c31 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Tue, 29 Sep 2026 09:35:12 +0200 Subject: [PATCH 10/17] fixup! Avoid `OverloadedStrings` extension --- src/Data/Text.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 0e666210..84abc0c2 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1921,8 +1921,8 @@ splitNE p t in l :| loop r where loop :: Text -> [Text] - loop "" = [] - loop s | null s' = [l'] + loop s | null s = [] + | null s' = [l'] | otherwise = l' : loop s' where (# l', s' #) = span_ (not . p) (unsafeTail s) {-# INLINE splitNE #-} From 833c9e35bbcfd15defb2181e4226b294d3422242 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Mon, 5 Oct 2026 15:41:06 +0200 Subject: [PATCH 11/17] Address feedback: avoid doc copy-and-paste --- src/Data/Text.hs | 15 ++------------- src/Data/Text/Internal/Lazy.hs | 4 ++-- src/Data/Text/Lazy.hs | 20 ++------------------ 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 84abc0c2..fe6d48bf 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1839,9 +1839,8 @@ splitOn pat splitOn (singleton c) t = split (==c) t #-} --- | /O(m+n)/ Break a 'Text' into pieces separated by the first 'Text' --- argument (which cannot be empty), consuming the delimiter. An empty --- delimiter is invalid, and will cause an error to be raised. +-- | Similar to 'splitOn', except that it returns @'NonEmpty' 'Text'@ instead +-- of @['Text']@.. -- -- Examples: -- @@ -1853,16 +1852,6 @@ splitOn pat -- -- >>> splitOnNE "x" "x" -- "" :| [""] --- --- and --- --- > intercalate s . splitOnNE s == id --- > splitOnNE (singleton c) == splitNE (==c) --- --- (Note: the string @s@ to split on above cannot be empty.) --- --- In (unlikely) bad cases, this function's time complexity degrades --- towards /O(n*m)/. splitOnNE :: HasCallStack => Text -- ^ String to split on. If this string is empty, an error diff --git a/src/Data/Text/Internal/Lazy.hs b/src/Data/Text/Internal/Lazy.hs index b3384070..5eade90f 100644 --- a/src/Data/Text/Internal/Lazy.hs +++ b/src/Data/Text/Internal/Lazy.hs @@ -53,8 +53,8 @@ data Text = Empty -- -- @since 2.1.2 | Chunk {-# UNPACK #-} !T.Text Text - -- ^ The @!T.Text@ field must be non-empty; this invariant is not - -- checked. See also 'chunk'. + -- ^ The first argument of @Chunk@ must be non-empty; this invariant + -- is not checked. See also 'chunk'. -- | Type synonym for the lazy flavour of 'Text'. -- diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index 4fb55e2c..64c05ed7 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -1639,10 +1639,8 @@ splitOn pat splitOn (singleton c) t = split (==c) t #-} --- | /O(m+n)/ Break a 'Text' into pieces separated by the first 'Text' --- argument (which cannot be an empty string), consuming the --- delimiter. An empty delimiter is invalid, and will cause an error --- to be raised. +-- | Similar to 'splitOn', except that it returns @'NonEmpty' 'Text'@ instead +-- of @['Text']@. -- -- Examples: -- @@ -1654,20 +1652,6 @@ splitOn pat -- -- >>> Data.Text.Lazy.splitOnNE "x" "x" -- "" :| [""] --- --- --- and --- --- > intercalate s . splitOnNE s == id --- > splitOnNE (singleton c) == splitNE (==c) --- --- (Note: the string @s@ to split on above cannot be empty.) --- --- This function is strict in its first argument, and lazy in its --- second. --- --- In (unlikely) bad cases, this function's time complexity degrades --- towards /O(n*m)/. splitOnNE :: HasCallStack => Text -- ^ String to split on. If this string is empty, an error From bbcbd6718ce1b6cc1c5a6e76d6f250c667934c74 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Mon, 5 Oct 2026 15:43:28 +0200 Subject: [PATCH 12/17] Avoid `Data.List.NonEmpty.singleton` --- src/Data/Text/Lazy.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index 64c05ed7..a3b04690 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -1702,7 +1702,7 @@ split p = NE.toList . splitNE p -- "" :| [] -- splitNE :: (Char -> Bool) -> Text -> NE.NonEmpty Text -splitNE _ Empty = NE.singleton Empty +splitNE _ Empty = Empty :| [] splitNE p (Chunk t0 ts0) = NE.fromList $ comb [] (T.splitNE p t0) ts0 where comb :: [T.Text] -> NE.NonEmpty T.Text -> Text -> [Text] comb acc (s :| []) Empty = revChunks (s:acc) : [] From 237baaaf343fa9ef6b2d6ee22fac65202abbe345 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Tue, 6 Oct 2026 00:07:38 +0200 Subject: [PATCH 13/17] Avoid using `NE.fromList` --- src/Data/Text.hs | 8 +++++--- src/Data/Text/Lazy.hs | 22 ++++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index fe6d48bf..79c3af57 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1862,10 +1862,12 @@ splitOnNE :: HasCallStack splitOnNE pat@(Text _ _ l) src@(Text arr off len) | null pat = emptyError "splitOnNE" | isSingleton pat = splitNE (== unsafeHead pat) src - | otherwise = NonEmptyList.fromList $ go 0 (indices pat src) + | otherwise = go 0 (indices pat src) where - go !s (x:xs) = text arr (s+off) (x-s) : go (x+l) xs - go s _ = [text arr (s+off) (len-s)] + go :: Int -> [Int] -> NonEmptyList.NonEmpty Text + go !s (x:xs) = NonEmptyList.cons (text arr (s+off) (x-s)) + (go (x+l) xs) + go s _ = text arr (s+off) (len-s) :| [] {-# INLINE [1] splitOnNE #-} {-# RULES diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index a3b04690..f355af48 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -5,6 +5,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Data.Text.Lazy @@ -1659,14 +1660,15 @@ splitOnNE :: HasCallStack -> Text -- ^ Input text. -> NE.NonEmpty Text -splitOnNE pat src - | null pat = emptyError "splitOnNE" - | isSingleton pat = splitNE (== head pat) src - | otherwise = NE.fromList $ go 0 (indices pat src) src +splitOnNE pat src = case uncons pat of + Nothing -> emptyError "splitOnNE" + Just (c, "") -> splitNE (== c) src + _ -> go 0 (indices pat src) src where - go _ [] cs = [cs] + go :: Int64 -> [Int64] -> Text -> NE.NonEmpty Text + go _ [] cs = cs :| [] go !i (x:xs) cs = let h :*: t = splitAtWord (x-i) cs - in h : (go (x+l) xs (dropWords l t)) + in NE.cons h $ go (x+l) xs (dropWords l t) l = foldlChunks (\a (T.Text _ _ b) -> a + intToInt64 b) 0 pat {-# INLINE [1] splitOnNE #-} @@ -1703,11 +1705,11 @@ split p = NE.toList . splitNE p -- splitNE :: (Char -> Bool) -> Text -> NE.NonEmpty Text splitNE _ Empty = Empty :| [] -splitNE p (Chunk t0 ts0) = NE.fromList $ comb [] (T.splitNE p t0) ts0 - where comb :: [T.Text] -> NE.NonEmpty T.Text -> Text -> [Text] - comb acc (s :| []) Empty = revChunks (s:acc) : [] +splitNE p (Chunk t0 ts0) = comb [] (T.splitNE p t0) ts0 + where comb :: [T.Text] -> NE.NonEmpty T.Text -> Text -> NE.NonEmpty Text + comb acc (s :| []) Empty = revChunks (s:acc) :| [] comb acc (s :| []) (Chunk t ts) = comb (s:acc) (T.splitNE p t) ts - comb acc (s :| ss : sss) ts = revChunks (s:acc) : comb [] (ss :| sss) ts + comb acc (s :| ss : sss) ts = NE.cons (revChunks (s:acc)) $ comb [] (ss :| sss) ts {-# INLINE splitNE #-} -- | /O(n)/ Splits a 'Text' into components of length @k@. The last From 6b840fe199e6b8cc8492ffbca948927579444381 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Tue, 6 Oct 2026 00:12:14 +0200 Subject: [PATCH 14/17] Address feedback: remove qualifiers in doctests and fix import in doctests --- src/Data/Text/Lazy.hs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index f355af48..c0c02ac8 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -306,7 +306,7 @@ import Text.Printf (PrintfArg, formatArg, formatString) -- $setup -- >>> :set -package transformers -- >>> import Control.Monad.Trans.State --- >>> import Data.Text +-- >>> import Data.Text.Lazy -- >>> import qualified Data.Text as T -- >>> :seti -XOverloadedStrings @@ -431,7 +431,7 @@ textDataType = mkDataType "Data.Text.Lazy.Text" [packConstr] -- -- Performs replacement on invalid scalar values, so @'unpack' . 'pack'@ is not 'id': -- --- >>> Data.Text.Lazy.unpack (Data.Text.Lazy.pack "\55555") +-- >>> unpack (pack "\55555") -- "\65533" pack :: #if defined(ASSERTS) @@ -1602,13 +1602,13 @@ tailsNE ts@(Chunk t ts') -- -- Examples: -- --- >>> Data.Text.Lazy.splitOn "\r\n" "a\r\nb\r\nd\r\ne" +-- >>> splitOn "\r\n" "a\r\nb\r\nd\r\ne" -- ["a","b","d","e"] -- --- >>> Data.Text.Lazy.splitOn "aaa" "aaaXaaaXaaaXaaa" +-- >>> splitOn "aaa" "aaaXaaaXaaaXaaa" -- ["","X","X","X",""] -- --- >>> Data.Text.Lazy.splitOn "x" "x" +-- >>> splitOn "x" "x" -- ["",""] -- -- and @@ -1645,13 +1645,13 @@ splitOn pat -- -- Examples: -- --- >>> Data.Text.Lazy.splitOnNE "\r\n" "a\r\nb\r\nd\r\ne" +-- >>> splitOnNE "\r\n" "a\r\nb\r\nd\r\ne" -- "a" :| ["b","d","e"] -- --- >>> Data.Text.Lazy.splitOnNE "aaa" "aaaXaaaXaaaXaaa" +-- >>> splitOnNE "aaa" "aaaXaaaXaaaXaaa" -- "" :| ["X","X","X",""] -- --- >>> Data.Text.Lazy.splitOnNE "x" "x" +-- >>> splitOnNE "x" "x" -- "" :| [""] splitOnNE :: HasCallStack => Text @@ -1682,10 +1682,10 @@ splitOnNE pat src = case uncons pat of -- resulting components do not contain the separators. Two adjacent -- separators result in an empty component in the output. eg. -- --- >>> Data.Text.Lazy.split (=='a') "aabbaca" +-- >>> split (=='a') "aabbaca" -- ["","","bb","c",""] -- --- >>> Data.Text.Lazy.split (=='a') "" +-- >>> split (=='a') "" -- [""] -- split :: (Char -> Bool) -> Text -> [Text] @@ -1697,10 +1697,10 @@ split p = NE.toList . splitNE p -- resulting components do not contain the separators. Two adjacent -- separators result in an empty component in the output. eg. -- --- >>> Data.Text.Lazy.splitNE (=='a') "aabbaca" +-- >>> splitNE (=='a') "aabbaca" -- "" :| ["","bb","c",""] -- --- >>> Data.Text.Lazy.splitNE (=='a') "" +-- >>> splitNE (=='a') "" -- "" :| [] -- splitNE :: (Char -> Bool) -> Text -> NE.NonEmpty Text From d7d77c585e06c13d5c7322d9ceb534a4e5f00274 Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Tue, 6 Oct 2026 00:20:27 +0200 Subject: [PATCH 15/17] Address feedback: use `uncons` --- src/Data/Text.hs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 79c3af57..d74adb48 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -6,6 +6,7 @@ {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -Wno-partial-type-signatures #-} @@ -1860,9 +1861,10 @@ splitOnNE :: HasCallStack -- ^ Input text. -> NonEmptyList.NonEmpty Text splitOnNE pat@(Text _ _ l) src@(Text arr off len) - | null pat = emptyError "splitOnNE" - | isSingleton pat = splitNE (== unsafeHead pat) src - | otherwise = go 0 (indices pat src) + = case uncons pat of + Nothing -> emptyError "splitOnNE" + Just (c, "") -> splitNE (== c) src + _ -> go 0 (indices pat src) where go :: Int -> [Int] -> NonEmptyList.NonEmpty Text go !s (x:xs) = NonEmptyList.cons (text arr (s+off) (x-s)) From 21d7960937d62c622c30b41a19dce180c67126bc Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Tue, 6 Oct 2026 11:40:15 +0200 Subject: [PATCH 16/17] fixup! Address feedback: avoid doc copy-and-paste --- src/Data/Text.hs | 6 ++---- src/Data/Text/Lazy.hs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index d74adb48..71cf3625 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -1893,10 +1893,8 @@ split :: (Char -> Bool) -> Text -> [Text] split p = NonEmptyList.toList . splitNE p {-# INLINE split #-} --- | /O(n)/ Splits a 'Text' into components delimited by separators, --- where the predicate returns True for a separator element. The --- resulting components do not contain the separators. Two adjacent --- separators result in an empty component in the output. eg. +-- | Similar to 'split', except that it returns @'NonEmpty' 'Text'@ instead of +-- @['Text']@. -- -- >>> splitNE (=='a') "aabbaca" -- "" :| ["","bb","c",""] diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index c0c02ac8..bf9f049d 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -1692,10 +1692,8 @@ split :: (Char -> Bool) -> Text -> [Text] split p = NE.toList . splitNE p {-# INLINE split #-} --- | /O(n)/ Splits a 'Text' into components delimited by separators, --- where the predicate returns True for a separator element. The --- resulting components do not contain the separators. Two adjacent --- separators result in an empty component in the output. eg. +-- | Similar to 'split', except that it returns @'NonEmpty' 'Text'@ instead of +-- @['Text']@. -- -- >>> splitNE (=='a') "aabbaca" -- "" :| ["","bb","c",""] From a6a483a3a37e6534f48c611a47d9ca0e559a201c Mon Sep 17 00:00:00 2001 From: Enrico Maria De Angelis Date: Thu, 8 Oct 2026 18:42:19 +0200 Subject: [PATCH 17/17] Avoid using `OverloadedStrings` --- src/Data/Text.hs | 3 +-- src/Data/Text/Lazy.hs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Data/Text.hs b/src/Data/Text.hs index 71cf3625..f029ebd4 100644 --- a/src/Data/Text.hs +++ b/src/Data/Text.hs @@ -6,7 +6,6 @@ {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -Wno-partial-type-signatures #-} @@ -1863,7 +1862,7 @@ splitOnNE :: HasCallStack splitOnNE pat@(Text _ _ l) src@(Text arr off len) = case uncons pat of Nothing -> emptyError "splitOnNE" - Just (c, "") -> splitNE (== c) src + Just (c, cs) | null cs -> splitNE (== c) src _ -> go 0 (indices pat src) where go :: Int -> [Int] -> NonEmptyList.NonEmpty Text diff --git a/src/Data/Text/Lazy.hs b/src/Data/Text/Lazy.hs index bf9f049d..2632da46 100644 --- a/src/Data/Text/Lazy.hs +++ b/src/Data/Text/Lazy.hs @@ -5,7 +5,6 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Data.Text.Lazy @@ -1662,7 +1661,7 @@ splitOnNE :: HasCallStack -> NE.NonEmpty Text splitOnNE pat src = case uncons pat of Nothing -> emptyError "splitOnNE" - Just (c, "") -> splitNE (== c) src + Just (c, cs) | null cs -> splitNE (== c) src _ -> go 0 (indices pat src) src where go :: Int64 -> [Int64] -> Text -> NE.NonEmpty Text