Skip to content

Add Data.List.compareLength #257

@Bodigrim

Description

@Bodigrim

Let's extend Data.List / Data.List.NonEmpty with new functions:

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

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

The idea is that compareLength xs n serves as a safer and faster alternative to compare (length xs) n. Similarly, it's better to write compareLength xs 10 == LT instead of length xs < 10. 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.

(This deficiency of length has been long recognised by warning STAN-0103 in stan)

> 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

Prior art:

The choice of name compareLength follows precedents in prior art, but also it's most natural to name a better version of (compare .) . length as compareLength.

MR can be found at https://gitlab.haskell.org/ghc/ghc/-/merge_requests/12822


  • How about generalizing from [a] and NonEmpty a to any Foldable?

    While the definition above uses only foldr and indeed works for any Foldable, it's hard to tell whether compareLength is better than (compare .) . length for a given Foldable or no. If length is O(1) then compare (length xs) n is faster than compareLength xs n which is O(n). Thus the proposal argues for adding compareLength only to containers for which length is known to be inefficient.

    Having the monomorphic Data.List.compareLength does not preclude anyone from raising a proposal to add Data.Foldable.compareLength or whatever they fancy to argue for.

  • How about generalizing from Int to any Num?

    I imagine that in the majority of cases n in compareLength xs n is a literal constant, so the change would force everyone to write an explicit type as in compareLength xs (10 :: Int), which will get very annoying soon. So let's stick to the usual convention of Int for length/indexing in Data.List.

    One could argue for genericlengthCompare xs n, polymorphic by n similar to the existing genericLength, but again, let me leave it to others to propose.

  • How about adding a rewrite rule changing compare (length xs) n to compareLength xs n?

    Such rule could affect semantics, making diverging programs terminate. While I'd expect such change to be a desirable one in the majority of cases, I'd rather leave it to a (potential) subsequent proposal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    approvedApproved by CLC votebase-4.21Implemented in base-4.21 (GHC 9.12)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions