5

In Haskell Data.Set implements a concrete set type. Normal [] lists also implement all operations of a set (in Data.List). But there seems to be no predefined Set typeclass that they both implement. You can implement one yourself:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}

module Set where

import qualified Data.List as ConcreteList
import qualified Data.Set as ConcreteSet

class Set set a | set -> a where
  empty :: set
  singleton :: a -> set
  insert :: a -> set -> set
  delete :: a -> set -> set
  union :: set -> set -> set
  intersection :: set -> set -> set
  member :: a -> set -> Bool
  filter :: (a -> Bool) -> set -> set

instance (Ord a) => Set (ConcreteSet.Set a) a where
  empty = ConcreteSet.empty
  singleton = ConcreteSet.singleton
  insert = ConcreteSet.insert
  delete = ConcreteSet.delete
  union = ConcreteSet.union
  intersection = ConcreteSet.intersection
  member = ConcreteSet.member
  filter = ConcreteSet.filter

instance (Eq a) => Set [a] a where
  empty = []
  singleton e = [e]
  insert = (:)
  delete = ConcreteList.delete
  union = ConcreteList.union
  intersection = ConcreteList.intersect
  member = ConcreteList.elem
  filter = ConcreteList.filter

but it seems that this would already have been done if this was the way to go. So my question is: Where is the Set typeclass or what alternative solution has the Haskell community come up with?

My question is admittedly quite similar to Why is Haskell missing “obvious” Typeclasses, but by being more concrete (focusing on a specific example with sample implementation), I'm hoping to get some more concrete answers as well.

Community
  • 1
  • 1
hkBst
  • 2,818
  • 10
  • 29
  • 2
    but those two behave differently: `delete 1 (insert 1 (insert 1 empty))` will be empty in one but not in the other... (which you can check with `member` if you argue that there is no equality implied) – Random Dev Jan 14 '16 at 13:42
  • @Carsten, you just found a bug in my implementation. – hkBst Jan 14 '16 at 13:45
  • 1
    ;) - it's just that I think of a very specific entity if I say `Set` (that's why I would call it collection or something – Random Dev Jan 14 '16 at 13:47
  • 1
    @Carsten Seems easy enough to fix by having `delete` for `List` delete *all* matching items, not just the first one. – user253751 Jan 15 '16 at 05:37
  • 1
    You might also be interested in the answers to [Why is Haskell missing "obvious" Typeclasses](http://stackoverflow.com/questions/25191659/why-is-haskell-missing-obvious-typeclasses) – Jonathan Cast Jan 15 '16 at 06:02

3 Answers3

7

For whatever reason, Haskell tends not to do the whole thing of having deep hierarchies of type classes for representing all possible sorts of containers.

Generally, different sorts of containers have performance properties for different operations. Typically you know what operations are important to you, and you choose the most suitable container and use that explicitly. There's not much requirement to be able to write really generic code that operates over every possible kind of container.

Note that there are some type classes for dealing with containers already — they're just not the ones you might be expecting. For example:

  • Functor allows you to apply a function to every element of a container — any container — and collect the results into a new container of the same type.

  • Applicative and/or Monad allow you to apply a function to every element and produce more than one element as result. (It may not be obvious, but you can use this to perform "filtering" and even "deletion", although it's probably not efficient.)

  • Monoid provides your empty and union methods (but named mempty and mappend).

  • Alternative, Foldable and Traversable let you do further interesting magic.

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • 6
    I reckon a major reason Haskell doesn't have so many superclasses is that, if you really need such a class, you can _at any point later_ define it yourself and make the suitable instances for any types you want. The type doesn't need to know anything about classes you put it in, unlike with Java where the types themselves are classes and need to specify their superclasses themselves. – leftaroundabout Jan 14 '16 at 15:32
  • 1
    Sometimes the needs of your program might change or perhaps aren't so clearcut to begin with, without testing which Set implementation has the best performance in your specific program. – hkBst Jan 15 '16 at 08:59
6

I'll annotate the class a bit:

-- The functional dependency here ties in to the issues with 
-- restricted type class instances, for which there is no one
-- globally adopted solution.
class Set set a | set -> a where
  -- This is a `Monoid`- or `Alternative`-like constant
  empty :: set

  -- This is an `Applicative`-like operation
  singleton :: a -> set

  -- If you have `singleton` and `union` you have `insert`.
  -- Which means that `insert` might fall out of the 
  -- `Alternative` class.
  insert :: a -> set -> set

  -- This looks like a variant of `filter` below.
  delete :: a -> set -> set

  -- These two are `Semigroup` operations
  union :: set -> set -> set
  intersection :: set -> set -> set

  -- Can't think of anything for this one.
  member :: a -> set -> Bool

  -- This is very `Monad`-like operation.  You can write this
  -- in terms of a bind/`unionMap` operation + your `singleton`
  -- and `empty`.
  filter :: (a -> Bool) -> set -> set

So basically, the community prefers a lot of small classes that are more reusable than this Set class would be.

Here's another point that might be useful. You seem to be mentally tying classes to types, as a mental classification of types that are similar because they "represent sets." But the Haskell community more often ties classes to operations. You can see this in the annotation above, I hope—most of the operations that you propose remind me of some already existing class. The black mark is the fact that most of those classes won't work with a type like Set that has an element type constraint...

Luis Casillas
  • 29,802
  • 7
  • 49
  • 102
  • I think your answer is the closest to the answer I'm looking for, but I'm still wondering how specifically you would choose to use this insight in the best possible way (in your opinion), to implement enough polymorphic operations that you can easily switch to a different implementation of a set, simply by changing the type of a function. – hkBst Jan 15 '16 at 09:23
5

You can try Data.Containers from mono-traversable

Haskell code in general tends to have less of a leaning towards generalizing data types like this than, say, Java, which is why these abstractions aren't in containers or unordered-containers.

Cactus
  • 27,075
  • 9
  • 69
  • 149
Michael Snoyman
  • 31,100
  • 3
  • 48
  • 77