
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