Search code examples
haskellsethaskell-lenslenses

Is there an appropriate optic for set membership?


I’m using Data.Sets in deeply-nested heterogeneous data structures, and thought it would be helpful to create a Prism for set membership. Hence:

membership :: (Ord a) => a -> Prism' (Set a) (Set a)
membership a = prism (Set.insert a) g
  where g as = if Set.member a as
               then Right $ Set.delete a as
               else Left as

However, this fails the first prism law, preview l (review l b) ≡ Just b, in the case where review l inserts a member that is already present in b, viz., if l is the membership lens for a, and b is {a}, then review l b is also {a}, and preview l (review l b) is just the null set, rather than just {a} as the first prism law requires.

Is there a better optic for capturing set membership? I like being able to check membership and conditionally decompose the set into the matching and non-matching parts simultaneously. Additionally, having an optic to do this is appealing because, since it captures all the use-cases I have for working with Sets in other parts of my code, it enables me to remove my import Data.Set statements from the rest of my package, which often indicates successful abstraction to me.


Solution

  • There is Contains type class with a member:

    contains :: Contains m => Index m -> Lens' m Bool
    

    which when specialised to Set is

    contains :: Ord a => a -> Lens' (Set a) Bool
    

    It is probably a good exercise to think why it is a lens (and not a prism, as in your attempt).