Question

I would like to understand the differences between the following two definitions of a Collection type class.

With multiparameter typeclasses and functional dependencies (taken from here);

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

class Eq e => Collection c e | c -> e where
    insert :: c -> e -> c
    member :: c -> e -> Bool

instance Eq a => Collection [a] a where
    insert = flip (:)
    member = flip elem

With only a single type parameter;

class Collection c where
    insert :: Eq e => c e -> e -> c e
    member :: Eq e => c e -> e -> Bool

instance Collection [] where
    insert = flip (:)
    member = flip elem

Both seem to compile and work ok. Are there any practical differences between the two, or any reason to favour one approach over the other?

Was it helpful?

Solution

A simple example where the Fundep version still works, but not the single-parameter one, is a Set container:

import qualified Data.Set as Set

instance (Ord e) => Collection (Set.Set e) e where
  insert = Set.insert
  member = flip Set.member

An instance to the other class doesn't work because you need an Ord constraint. Now, you could change the definitions to

class Collection c where
    insert :: Ord e => c e -> e -> c e
    member :: Ord e => c e -> e -> Bool

But for simpler containers like lists that would merely be a nuisance, you'd like to also store non-ord types there. For Hashmaps you need yet another constraint. It's no good making those global and required for any collection.

OTHER TIPS

Another usecase your second variant won't work for is monomorphic containers. E.g., a ByteString can be seen as a container of Chars.

It should also be noted that there is a third alternative based on type families, which is the most flexible one. Though it won't make much difference from fundeps-based one in this trivial case.

{-# LANGUAGE TypeFamilies #-}

import qualified Data.ByteString.Char8 as B

class Collection c where
  type Row c
  insert :: c -> Row c -> c
  member :: c -> Row c -> Bool

instance (Eq a) => Collection [a] where
  type Row [a] = a
  insert = flip (:)
  member = flip elem

instance Collection B.ByteString where
  type Row B.ByteString = Char
  insert = flip B.cons
  member = flip B.elem
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top