[Git][ghc/ghc][wip/Data.List.compareLength] Implement Data.List.compareLength and Data.List.NonEmpty.compareLength

Bodigrim (@Bodigrim) gitlab at gitlab.haskell.org
Mon Jun 24 19:36:56 UTC 2024



Bodigrim pushed to branch wip/Data.List.compareLength at Glasgow Haskell Compiler / GHC


Commits:
39c40e6c by Andrew Lelechenko at 2024-06-24T20:36:41+01:00
Implement Data.List.compareLength and Data.List.NonEmpty.compareLength

`compareLength xs n` is a safer and faster alternative to `compare (length xs) n`.
The latter would force and traverse the entire spine (potentially diverging),
while the former traverses as few elements as possible.

The implementation is carefully designed to maintain as much laziness as possible.

As per https://github.com/haskell/core-libraries-committee/issues/257

- - - - -


11 changed files:

- libraries/base/changelog.md
- libraries/base/src/Data/List.hs
- libraries/base/src/Data/List/NonEmpty.hs
- testsuite/tests/interface-stability/base-exports.stdout
- testsuite/tests/interface-stability/base-exports.stdout-javascript-unknown-ghcjs
- testsuite/tests/interface-stability/base-exports.stdout-mingw32
- testsuite/tests/interface-stability/base-exports.stdout-ws-32
- + testsuite/tests/lib/base/CompareLength.hs
- + testsuite/tests/lib/base/CompareLength.stdout
- testsuite/tests/lib/base/Unsnoc.hs
- testsuite/tests/lib/base/all.T


Changes:

=====================================
libraries/base/changelog.md
=====================================
@@ -2,6 +2,7 @@
 
 ## 4.21.0.0 *TBA*
   * Add a `readTixFile` field to the `HpcFlags` record in `GHC.RTS.Flags` ([CLC proposal #276](https://github.com/haskell/core-libraries-committee/issues/276))
+  * Add `compareLength` to `Data.List` and `Data.List.NonEmpty` ([CLC proposal #257](https://github.com/haskell/core-libraries-committee/issues/257))
   * Add `INLINE[1]` to `compareInt` / `compareWord` ([CLC proposal #179](https://github.com/haskell/core-libraries-committee/issues/179))
   * Add the `MonadFix` instance for `(,) a`, similar to the one for `Writer a` ([CLC proposal #238](https://github.com/haskell/core-libraries-committee/issues/238))
   * Improve `toInteger :: Word32 -> Integer` on 64-bit platforms ([CLC proposal #259](https://github.com/haskell/core-libraries-committee/issues/259))


=====================================
libraries/base/src/Data/List.hs
=====================================
@@ -26,6 +26,7 @@ module Data.List
      singleton,
      null,
      length,
+     compareLength,
      -- *  List transformations
      map,
      reverse,
@@ -178,8 +179,12 @@ module Data.List
      genericReplicate
      ) where
 
+import GHC.Internal.Data.Bool (otherwise)
 import GHC.Internal.Data.List
 import GHC.Internal.Data.List.NonEmpty (NonEmpty(..))
+import GHC.Internal.Data.Ord (Ordering(..), (<), (>))
+import GHC.Internal.Int (Int)
+import GHC.Internal.Num ((-))
 import GHC.List (build)
 
 inits1, tails1 :: [a] -> [NonEmpty a]
@@ -243,3 +248,37 @@ tails1 lst = build (\c n ->
   let tails1Go [] = n
       tails1Go (x : xs) = (x :| xs) `c` tails1Go xs
   in tails1Go lst)
+
+-- | Use 'compareLength' @xs@ @n@ as a safer and faster alternative
+-- to 'compare' ('length' @xs@) @n at . Similarly, it's better
+-- to write @compareLength xs 10 == LT@ instead of @length xs < 10 at .
+--
+-- While 'length' would force and traverse
+-- the entire spine of @xs@ (which could even diverge if @xs@ is infinite),
+-- 'compareLength' traverses at most @n@ elements to determine its result.
+--
+-- >>> compareLength [] 0
+-- EQ
+-- >>> compareLength [] 1
+-- LT
+-- >>> compareLength ['a'] 1
+-- EQ
+-- >>> compareLength ['a', 'b'] 1
+-- GT
+-- >>> compareLength [0..] 100
+-- GT
+-- >>> compareLength undefined (-1)
+-- GT
+-- >>> compareLength ('a' : undefined) 0
+-- GT
+--
+-- @since 4.21.0.0
+--
+compareLength :: [a] -> Int -> Ordering
+compareLength xs n
+  | n < 0 = GT
+  | otherwise = foldr
+    (\_ f m -> if m > 0 then f (m - 1) else GT)
+    (\m -> if m > 0 then LT else EQ)
+    xs
+    n


=====================================
libraries/base/src/Data/List/NonEmpty.hs
=====================================
@@ -36,6 +36,7 @@ module Data.List.NonEmpty (
    , sortWith      -- :: Ord o => (a -> o) -> NonEmpty a -> NonEmpty a
    -- * Basic functions
    , length      -- :: NonEmpty a -> Int
+   , compareLength -- :: NonEmpty a -> Int -> Ordering
    , head        -- :: NonEmpty a -> a
    , tail        -- :: NonEmpty a -> [a]
    , last        -- :: NonEmpty a -> a
@@ -128,6 +129,36 @@ infixr 5 <|
 length :: NonEmpty a -> Int
 length (_ :| xs) = 1 + Prelude.length xs
 
+-- | Use 'compareLength' @xs@ @n@ as a safer and faster alternative
+-- to 'compare' ('length' @xs@) @n at . Similarly, it's better
+-- to write @compareLength xs 10 == LT@ instead of @length xs < 10 at .
+--
+-- While 'length' would force and traverse
+-- the entire spine of @xs@ (which could even diverge if @xs@ is infinite),
+-- 'compareLength' traverses at most @n@ elements to determine its result.
+--
+-- >>> compareLength ('a' :| []) 1
+-- EQ
+-- >>> compareLength ('a' :| ['b']) 3
+-- LT
+-- >>> compareLength (0 :| [1..]) 100
+-- GT
+-- >>> compareLength undefined 0
+-- GT
+-- >>> compareLength ('a' :| 'b' : undefined) 1
+-- GT
+--
+-- @since 4.21.0.0
+--
+compareLength :: NonEmpty a -> Int -> Ordering
+compareLength xs n
+  | n < 1 = GT
+  | otherwise = foldr
+    (\_ f m -> if m > 0 then f (m - 1) else GT)
+    (\m -> if m > 0 then LT else EQ)
+    xs
+    n
+
 -- | Compute n-ary logic exclusive OR operation on 'NonEmpty' list.
 xor :: NonEmpty Bool -> Bool
 xor (x :| xs)   = foldr xor' x xs


=====================================
testsuite/tests/interface-stability/base-exports.stdout
=====================================
@@ -1299,6 +1299,7 @@ module Data.List where
   and :: forall (t :: * -> *). GHC.Internal.Data.Foldable.Foldable t => t GHC.Types.Bool -> GHC.Types.Bool
   any :: forall (t :: * -> *) a. GHC.Internal.Data.Foldable.Foldable t => (a -> GHC.Types.Bool) -> t a -> GHC.Types.Bool
   break :: forall a. (a -> GHC.Types.Bool) -> [a] -> ([a], [a])
+  compareLength :: forall a. [a] -> GHC.Types.Int -> GHC.Types.Ordering
   concat :: forall (t :: * -> *) a. GHC.Internal.Data.Foldable.Foldable t => t [a] -> [a]
   concatMap :: forall (t :: * -> *) a b. GHC.Internal.Data.Foldable.Foldable t => (a -> [b]) -> t a -> [b]
   cycle :: forall a. GHC.Internal.Stack.Types.HasCallStack => [a] -> [a]
@@ -1423,6 +1424,7 @@ module Data.List.NonEmpty where
   append :: forall a. NonEmpty a -> NonEmpty a -> NonEmpty a
   appendList :: forall a. NonEmpty a -> [a] -> NonEmpty a
   break :: forall a. (a -> GHC.Types.Bool) -> NonEmpty a -> ([a], [a])
+  compareLength :: forall a. NonEmpty a -> GHC.Types.Int -> GHC.Types.Ordering
   cons :: forall a. a -> NonEmpty a -> NonEmpty a
   cycle :: forall a. NonEmpty a -> NonEmpty a
   drop :: forall a. GHC.Types.Int -> NonEmpty a -> [a]


=====================================
testsuite/tests/interface-stability/base-exports.stdout-javascript-unknown-ghcjs
=====================================
@@ -1299,6 +1299,7 @@ module Data.List where
   and :: forall (t :: * -> *). GHC.Internal.Data.Foldable.Foldable t => t GHC.Types.Bool -> GHC.Types.Bool
   any :: forall (t :: * -> *) a. GHC.Internal.Data.Foldable.Foldable t => (a -> GHC.Types.Bool) -> t a -> GHC.Types.Bool
   break :: forall a. (a -> GHC.Types.Bool) -> [a] -> ([a], [a])
+  compareLength :: forall a. [a] -> GHC.Types.Int -> GHC.Types.Ordering
   concat :: forall (t :: * -> *) a. GHC.Internal.Data.Foldable.Foldable t => t [a] -> [a]
   concatMap :: forall (t :: * -> *) a b. GHC.Internal.Data.Foldable.Foldable t => (a -> [b]) -> t a -> [b]
   cycle :: forall a. GHC.Internal.Stack.Types.HasCallStack => [a] -> [a]
@@ -1423,6 +1424,7 @@ module Data.List.NonEmpty where
   append :: forall a. NonEmpty a -> NonEmpty a -> NonEmpty a
   appendList :: forall a. NonEmpty a -> [a] -> NonEmpty a
   break :: forall a. (a -> GHC.Types.Bool) -> NonEmpty a -> ([a], [a])
+  compareLength :: forall a. NonEmpty a -> GHC.Types.Int -> GHC.Types.Ordering
   cons :: forall a. a -> NonEmpty a -> NonEmpty a
   cycle :: forall a. NonEmpty a -> NonEmpty a
   drop :: forall a. GHC.Types.Int -> NonEmpty a -> [a]


=====================================
testsuite/tests/interface-stability/base-exports.stdout-mingw32
=====================================
@@ -1299,6 +1299,7 @@ module Data.List where
   and :: forall (t :: * -> *). GHC.Internal.Data.Foldable.Foldable t => t GHC.Types.Bool -> GHC.Types.Bool
   any :: forall (t :: * -> *) a. GHC.Internal.Data.Foldable.Foldable t => (a -> GHC.Types.Bool) -> t a -> GHC.Types.Bool
   break :: forall a. (a -> GHC.Types.Bool) -> [a] -> ([a], [a])
+  compareLength :: forall a. [a] -> GHC.Types.Int -> GHC.Types.Ordering
   concat :: forall (t :: * -> *) a. GHC.Internal.Data.Foldable.Foldable t => t [a] -> [a]
   concatMap :: forall (t :: * -> *) a b. GHC.Internal.Data.Foldable.Foldable t => (a -> [b]) -> t a -> [b]
   cycle :: forall a. GHC.Internal.Stack.Types.HasCallStack => [a] -> [a]
@@ -1423,6 +1424,7 @@ module Data.List.NonEmpty where
   append :: forall a. NonEmpty a -> NonEmpty a -> NonEmpty a
   appendList :: forall a. NonEmpty a -> [a] -> NonEmpty a
   break :: forall a. (a -> GHC.Types.Bool) -> NonEmpty a -> ([a], [a])
+  compareLength :: forall a. NonEmpty a -> GHC.Types.Int -> GHC.Types.Ordering
   cons :: forall a. a -> NonEmpty a -> NonEmpty a
   cycle :: forall a. NonEmpty a -> NonEmpty a
   drop :: forall a. GHC.Types.Int -> NonEmpty a -> [a]


=====================================
testsuite/tests/interface-stability/base-exports.stdout-ws-32
=====================================
@@ -1299,6 +1299,7 @@ module Data.List where
   and :: forall (t :: * -> *). GHC.Internal.Data.Foldable.Foldable t => t GHC.Types.Bool -> GHC.Types.Bool
   any :: forall (t :: * -> *) a. GHC.Internal.Data.Foldable.Foldable t => (a -> GHC.Types.Bool) -> t a -> GHC.Types.Bool
   break :: forall a. (a -> GHC.Types.Bool) -> [a] -> ([a], [a])
+  compareLength :: forall a. [a] -> GHC.Types.Int -> GHC.Types.Ordering
   concat :: forall (t :: * -> *) a. GHC.Internal.Data.Foldable.Foldable t => t [a] -> [a]
   concatMap :: forall (t :: * -> *) a b. GHC.Internal.Data.Foldable.Foldable t => (a -> [b]) -> t a -> [b]
   cycle :: forall a. GHC.Internal.Stack.Types.HasCallStack => [a] -> [a]
@@ -1423,6 +1424,7 @@ module Data.List.NonEmpty where
   append :: forall a. NonEmpty a -> NonEmpty a -> NonEmpty a
   appendList :: forall a. NonEmpty a -> [a] -> NonEmpty a
   break :: forall a. (a -> GHC.Types.Bool) -> NonEmpty a -> ([a], [a])
+  compareLength :: forall a. NonEmpty a -> GHC.Types.Int -> GHC.Types.Ordering
   cons :: forall a. a -> NonEmpty a -> NonEmpty a
   cycle :: forall a. NonEmpty a -> NonEmpty a
   drop :: forall a. GHC.Types.Int -> NonEmpty a -> [a]


=====================================
testsuite/tests/lib/base/CompareLength.hs
=====================================
@@ -0,0 +1,21 @@
+module Main (main) where
+
+import qualified Data.List as L
+import Data.List.NonEmpty (NonEmpty(..))
+import qualified Data.List.NonEmpty as NE
+
+main :: IO ()
+main = do
+  print $ L.compareLength [] 0
+  print $ L.compareLength [] 1
+  print $ L.compareLength ['a'] 1
+  print $ L.compareLength ['a', 'b'] 1
+  print $ L.compareLength [0..] 100
+  print $ L.compareLength undefined (-1)
+  print $ L.compareLength ('a' : undefined) 0
+
+  print $ NE.compareLength ('a' :| []) 1
+  print $ NE.compareLength ('a' :| ['b']) 3
+  print $ NE.compareLength (0 :| [1..]) 100
+  print $ NE.compareLength undefined 0
+  print $ NE.compareLength ('a' :| 'b' : undefined) 1


=====================================
testsuite/tests/lib/base/CompareLength.stdout
=====================================
@@ -0,0 +1,12 @@
+EQ
+LT
+EQ
+GT
+GT
+GT
+GT
+EQ
+LT
+GT
+GT
+GT


=====================================
testsuite/tests/lib/base/Unsnoc.hs
=====================================
@@ -1,6 +1,6 @@
 {-# OPTIONS_GHC -Wno-x-partial #-}
 
-module Main (main) where 
+module Main (main) where
 
 import Data.List (unsnoc)
 


=====================================
testsuite/tests/lib/base/all.T
=====================================
@@ -13,3 +13,4 @@ test('First-Semigroup-sconcat', normal, compile_and_run, [''])
 test('First-Monoid-sconcat', normal, compile_and_run, [''])
 test('Sort', normal, compile_and_run, [''])
 test('InitsTails', normal, compile_and_run, [''])
+test('CompareLength', normal, compile_and_run, [''])



View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/39c40e6c49ca8deaac7fd00fbd325520f5f0f6d3

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/39c40e6c49ca8deaac7fd00fbd325520f5f0f6d3
You're receiving this email because of your account on gitlab.haskell.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20240624/430c1fc6/attachment-0001.html>


More information about the ghc-commits mailing list