[Haskell-cafe] What's wrong with the classes/insances?

wren ng thornton wren at freegeek.org
Fri Jun 20 18:49:17 EDT 2008


Pieter Laeremans wrote:
> HI,
> 
> What 's wrong with this:
> 
> [...]
> 
> class Item a where
>     getCatalog :: Catalog catalog => a -> catalog

This is a shorthand for

  > class Item a where
  >     getCatalog :: forall c. (Catalog c) => a -> c

That is, the class provides the contract that given some value of type a 
it will be able to return *any* type c which adheres to the contract of 
the class Catalog. This function is polymorphic in the return type.


> instance Catalog c => Item (Content c) where
>    getCatalog (Content  _ _ c) = c

The problem is, here you're returning a *specific* type c which adheres 
to Catalog. What happens if the caller of getCatalog is expecting some 
other type (Catalog c') => c' or (Catalog c'') => c'' etc?


There are a few different solutions you could take. The easiest one is 
to use multi-parameter type classes and functional dependencies to 
define Item like so:

  > class Item a c | a -> c where
  >     getCatalog :: (Catalog c) => a -> c

This says that for any given type a there is one particular type c which 
getCatalog returns. Depending on your goals this may be enough, but if 
you really want getCatalog to be polymorphic in c then it won't work. 
(If you only want to be, er, multimorphic in c then you can leave out 
the fundep and define instances for each a*c type pair. This can lead to 
needing to declare excessively many type signatures however.)


If you really want to be able to return any c then there are a couple of 
approaches you could take. First is to add a conversion function to the 
Catalog class:

  > class Catalog c where
  >     ...
  >     convertCatalog:: forall c'. (Catalog c') => c -> c'

Given the rest of the definition for Catalog, this looks to be eminently 
doable-- at least in as far as people don't try to access any other 
fiddly bits inside the value c'. Of course this gives no way of 
preventing them from trying to do that fiddling, which leads to...


The other common approach is to use existential types to wrap values up 
with their class dictionaries like so:

  > data CatalogWrapper = forall c. (Catalog c) => CatalogWrapper c

In this case you'd have getCatalog return a CatalogWrapper instead of a 
(Catalog c) => c. If you're not familiar with existential types they can 
be harder to think about since they loose information about what type 
you actually have inside; once wrapped they're only ever accessible by 
the methods of type classes restricting what we can wrap. But if you 
want to restrict people to only ever using the Catalog interface to 
manipulate them, then this is exactly what you want.


-- 
Live well,
~wren


More information about the Haskell-Cafe mailing list