| > > class (Eq key, Ord key) => Dictionary dict key dat where
| > > delete :: key -> dict -> dict
| ...
| > the first error:
| >
| > Class type variable `dat' does not appear in method signature
| > delete :: key -> dict -> dict
| >
| > Why does ghc expect that I use all of the type variables?
| > Obviously I only need
| > the key to remove an entry of a dictionary.
|
| You're right. The restriction is excessive. Thanks for pointing
| this out. Probably we should only require that at least one
| of the class variables is constrained.
I don't see this. The fact is that, if any of the variables in the
class header don't appear in the type of a method, then that method
will always have an ambiguous type. In this case, that would be:
delete :: (Dictionary dict key dat) => key -> dict -> dict
Overloading is usually resolved by looking at the context in which
an identifier is used (in other words, by looking at the types that
a functions arguments and results are expected to have). The problem
with this example is that there will never be any way for the type
system to determine which value the type variable `dat' should take.
Technically, this results in a loss of coherence (i.e., an undefined
semantics) for any program involving the function delete. The type
checker might as well flag the declaration of delete as an error,
because you won't ever be able to use it without producing an error.
The solution to such problems is usually to create a small hierarchy
of classes (with different numbers of parameters) such as:
class DictWithDelete key dict where
delete :: key -> dict -> dict
class DictWithDelete key dict => Dictionary key dict dat where
add :: key -> dat -> dict -> dict
etc...
However, in this case, using a constructor class as Simon suggested is
almost certainly the best approach.
I don't see how Simon's suggestion of replacing "all" with "at least one"
could be made to work in general. Such an approach is however possible
if you are working with multiple parameter classes where the variables in
one parameter are determined directly by the parameters in another.
For example, consider the following class:
class Collection elem col where
empty :: col
add :: elem -> col -> col
del :: elem -> col -> col
enum :: col -> [elem]
By the argument above, one should expect empty to be rejected as having
an ambiguous type: Collection elem col => col. However, we could also
imagine modifying the definition of ambiguity to take account of the fact
that, in practice, the value of elem would probably be uniquely determined
by the value of col, and hence the context in which empty is used could
still provide enough information to allow the overloading to be resolved.
This is one of the key ideas behind Chen, Hudak and Odersky's proposal
for parametric classes. The weaker notion of ambiguity here is just what
the AV operator (in my own thesis) was about. Note that this would require
a new extension to the syntax of class declarations; at the moment, there
is no way to express any kind of dependency between the parameters of a
class. (Nor are there mechanisms in the compilers to enforce them!)
All the best,
Mark