[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:
> 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
> 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.
More information about the Haskell-Cafe