Skip to content
GitLab
Projects Groups Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
  • GHC GHC
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 5,413
    • Issues 5,413
    • List
    • Boards
    • Service Desk
    • Milestones
    • Iterations
  • Merge requests 600
    • Merge requests 600
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
    • Test Cases
  • Deployments
    • Deployments
    • Releases
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Code review
    • Insights
    • Issue
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • Glasgow Haskell CompilerGlasgow Haskell Compiler
  • GHCGHC
  • Issues
  • #20421
Closed
Open
Issue created Sep 27, 2021 by vdukhovni@trac-vdukhovniDeveloper

The `elem` method of `Foldable` is a poor abstraction for structures with faster than linear search

Motivation

The Foldable class generalises various operations from List (i.e. []) that are mostly inherently linear in the structure size, or (in the case of e.g. length) can be optimised to an O(1) operation for some structures.

Many of the methods carry no additional type constraints on the element type, or carry constraints that are natural (Num for sum and product, Monoid for fold, ...). If we look at the full method list, just one (elem) has a constraint that is not necessarily the natural one for every structure:

class Foldable t where
  Data.Foldable.fold :: Monoid m => t m -> m
  foldMap :: Monoid m => (a -> m) -> t a -> m
  Data.Foldable.foldMap' :: Monoid m => (a -> m) -> t a -> m
  foldr :: (a -> b -> b) -> b -> t a -> b
  Data.Foldable.foldr' :: (a -> b -> b) -> b -> t a -> b
  foldl :: (b -> a -> b) -> b -> t a -> b
  Data.Foldable.foldl' :: (b -> a -> b) -> b -> t a -> b
  foldr1 :: (a -> a -> a) -> t a -> a
  foldl1 :: (a -> a -> a) -> t a -> a
  Data.Foldable.toList :: t a -> [a]
  null :: t a -> Bool
  length :: t a -> Int
* elem :: Eq a => a -> t a -> Bool
  maximum :: Ord a => t a -> a
  minimum :: Ord a => t a -> a
  sum :: Num a => t a -> a
  product :: Num a => t a -> a

The issue here is that with just Eq a (barring GADT existentials) we can't do better than a linear search, comparing each element with the desired one. And so, even though Data.Set.Set supports O(log n) search, its elem instance is sadly linear, for lack of an Ord a dictionary.

In !6555 (closed), a work-around is described, where one abandons direct use of elem by introduing a multi-parameter type class, along the lines of:

import qualified Data.Set as Set

class Eq a => HasMember t a where
    member :: a -> t a -> Bool

instance Eq a => HasMember [] a where
    member = elem
[...]
instance Ord a => HasMember Set.Set a where
    member = Set.member

in which instances can specify any additional requisite constraints on the element type, because it appears in the class head along with the structure type.

A fair criticism of the documentation is that the user should reasonably be able to find this class somewhere, rather than being told to how to implement it. That is, it should be in some existing module in base, and packages like containers can then specify appropriate instances.

For now, I think it still makes sense to document that status quo, imperfect as it may be. That said, elem would ideally not have been a Foldable method, but should have been in a separate class, that could support element-type-specific constraints.

Proposal

Consider introducing a new class in base that supports a performant elem-like operation across various container types, taking advantage of any available speedups that the structure in question might support.

Or do nothing??? [ And leave it to users who want a polymorphic member function to implement any additional requisite type class and instances. ]

Or something else?

Cc: @rae , @Kleidukos , @carter

Edited Sep 28, 2021 by vdukhovni
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Assignee
Assign to
Time tracking