Marcin 'Qrczak' Kowalczyk <[EMAIL PROTECTED]> writes on 14 Feb 2000
> [..]
> overlapping instances don't solve what they seemed to claim to solve:
>
> class A a where ...
> class A a => B a where ...
> class C a where ...
>
> instance A a => C a where ...
> instance B a => C a where ...
>
> How to specify which C instance to apply?
> ghc -fallow-{overlapping,undecidable}-instances complains
> about the ambiguity.
Here A is a superclass for B, so, `B a =>' is a more special
condition than `A a =>'.
Hence, instance B a => C a ...
has to apply.
If this approach is sound enough, I hope, the implementors would
apprehend it.
> I simply don't accept the situation when adding an instance or
> declaration changes the meaning of a code that was legal before.
Thus, in the above example, adding instance B a => C a ...
does not change the real meaning of the code.
Because the values evaluate to the same result, maybe, in different
ways. This is functional programming: the result matters, not the
way to obtain it.
For example, det [[1,2],[0,3]] has always to evaluate to 3,
whatever instances had been added (and really applied) for the
different ways to compute det.
> [..]
> In the presence of such things I must sometimes scan the
> whole program for things that might change the meaning of the code,
> by e.g. specifying more specific instances, or by making an instance
> that caused another instance to apply to a particular type. The same
> kind of danger as with SML's constructor: one cannot be sure unless
> he has proven the lack of something in the whole program.
Maybe, overlaps does not change radically the situation. In any
case, when the compiler sees some operation op of class C in the
module M, one has to find the instance(s) for C in the modules
visible from M. The difference is, probably, in that with the
overlapping instances, the compiler has not right to stop after the
first found instance.
Right?
>> >> If the user puts so, then one proclaims that it is the same for the
>> >> result which way to choose.
>>
>> > The compiler can't verify that, so I accept that it simply raises
>> > an error, at least by default.
>>
>> No, no. There are many things that the compiler cannot or would
>> not verify. This is not a reason for reporting an error each time.
> But IMHO it is most likely an error to make two instances of the given
> class and types. Someone might have seen only one of these instances
> and thought that it does apply. I could write one of those instances
> not knowing the other, and the compiler silently picked one of them,
> maybe not the one I wanted... The compiler does not know if they
> really behave the same or not. It would be dangerous to assume that
> they always do.
Example: when programming the instance with the determinant det of
a matrix for some special case, the programmer has *not* to search
for other instances with det. This is because the value of det
would be the same for all the methods to compute it - at the common
domain of applicability of these methods.
I think, the overlapping instances are only for such kind of
situation. If the user likes trouble, of course, one can write bad
overlaps. But this is with the many other language features as well.
>> > This means that these cases are not "generic" and "special"
>> > respectively. The essence of being generic is that the same thing
>> > is applicable to many cases. The essence of being a special case of
>> > something is that everything that is applicable generically to that
>> > thing applies to us as well, but not necessarily the opposite.
>>
>> I meant that the generic implementation works at a wider range
>> of cases.
> It does not, because sometimes a more specialized one replaces it.
The generic implementation - when considered alone - works at a
wider range than the special one (when considered alone).
This was the meaning.
>> instance Euclidean a => Set (ResidueE a)
>> where
>> card (Rse _ b) = expensiveGenericMethodForCard b
>> ...
>> instance Set (ResidueE Integer) where card (Rse _ b) = Fin b
>> ...
>> instance Field a => Set (ResidueE (UPol a)) where
>> card _ = Infinity
>> ...
> The first instance is a problem, because it does not apply for
> all cases of ResidueE a (or at least is too expensive to be used
> for all cases).
The first instance can apply to all cases, but for the special cases
there exist the cheaper methods.
I do not see a problem here. This is the usual situation with
solving a task. A task is how to compute something. We know how to
compute it simply in the special case and know how to compute it in
an expensive way in the generic case.
> I guess that instances of set should be separately
> specified for various types of the form ResidueE x where x is some
> more concrete type. Some of them will be similar, just calling
> expensiveGenericMethodForCard. This has the disadvantage that
> an instance must be repeated for all types that require distinct
> implementations, but the advantage is using a clear type system
The data classes were introduced just to avoid such unnatural
programming style.
>> > If a function should not be used for a particular type, the type
>> > should not have been declared as an instance of some appropriate class.
>>
>> According to the above settings, expensiveGenericMethodForCard
>> should not be used for a particular type ResidueE Integer.
> Yes.
>> Then, as you suggest ResidueE Integer
>> should not have been declared as an instance of Set.
> No, because expensiveGenericMethodForCard should not have been
> used in the first place in the general instance. There should be no
> general instance because some specific instances require a different
> implementation.
To my mind, this latter restriction does not look natural.
> Or, especially if there are several classes like Set that should
> have generic instances for ResidueE a and specific instances for
> specialized variants of ResidueE (but not necessarily), then it may
> help to group properties of ResidueE in a single class, make a generic
> Set instance of ResidueE for types that belong to that class, similarly
> to all the other classes like Set, and then play with instances of that
> class. There can be default method implementations, so some (or most)
> instances will need not to define any methods explicitly. I think
> that this all causes no problem for quite standard Haskell.
Something hard to understand. I do not see why one has to play
these strange tricks instead of introducing most natural overlapping
instances.
The scientific computation has thousands years experience; it often
sets the computation scheme in the style of overlapping instances.
The compilers are 40 years old. It is a good point for them to
apprehend a piece of accumulated culture.
------------------
Sergey Mechveliani
[EMAIL PROTECTED]
http://www.botik.ru/~mechvel