Sun, 6 Feb 2000 20:33:52 +0300 (MSK), S.D.Mechveliani <[EMAIL PROTECTED]> pisze:

> It is clear from the example of my previous letter that some
> operation may be computed in different ways in general and in
> several special situations. This leads to overlapping instances.

This does not *require* overlapping or undecidable instances. They may
only be convenient to avoid writing instances manually for each type.

> It may know how to compute an operation in a more efficient way in 
> the special case, and in a less efficient way in the generic case.

But in a case where it's only a matter of efficiency, it is not so
dangerous if careful imports etc. can fix it.

(I would try to avoid using overlapping instances in any case,
because I don't believe in their sane semantics.)

> > If it does not know, it should not specify it.
> 
> There are often several operations in a class. Each operation may
> have its own most generic situation in which it is known how to
> compute. This does not look like a sufficient reason to split this
> class into several classes, each with a single operation.

Sorry, life is hard. Anyway, I don't see a place for returning Unknown
values. Should really the fact that cardinality is known or not be
computed at runtime? Why is cardinality not in a separate class which
have instances *only* for types where it is known?

> A good example for overlapping instances: the matrix determinant:
> 
>                det mt,  mt :: [[a]]  n by n  matrix.
> 
> I know the following good ways to compute it.

I would probably:
- define separate functions for each algorithm, each with correct
  requirements in its type,
- define a class with a generic determinant function,
- make its instances for each possible type or type constructor,
  manually choosing the algorithm that fits best.

> > Probably the system of Haskell classes is not well suited for
> > instances like "if it is ever X, it's certainly Z as well and
> > the implementation of Z depends on the implementation of X, but
> > generally X is not needed for Z". Standard Haskell does not allow
> > such things and not without a reason.
> 
> So far, I never observed or realized such reason.
> The above trouble with forgetting of import is not enough.

It fails completely in the case I mentioned: "X => Z, Y => Z, X, Y".

Requires overlapping instances in a case where in fact something is
X and the way it should be Z is different than that induced by X.
And overlapping instances themselves can be lost because of context
reduction, where a polymorphic function will use the generic instance
of something else instead of the specific one - I can think about an
example if it's unclear.

Generally it does not fit into my mental model of a Haskell class.
How could it be that adding an instance *constrains* what else we
can do with a type (define an instance of another class)?

> Forgetting to import something may cause, say, slower computation
> at the run time.

IMHO forgetting to import something should only cause compile errors.
The smallest import list should be a function of the module body
(modulo the same entity being exported by several modules, and of
course modulo name clashes).

> > There are ambiguities if something was defined as "if it's X then
> > it's Z, if it's Y then it's Z, it's X, it's Y" - but *how* it 
> > should be Z - basing on X or Y?
> 
> 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.

At most it could be explicitly marked by the programmer that whenever
there is an ambiguity between certain definitions, it does not matter
which to use because they all produce the same result. It would
require some thought about a good interface of such specifications. We
should ensure that there are cases when it's really important to be
able to give several definitions to let the compiler choose from,
and that we won't complain if it chooses not what we expected. I'm
not convinced. It's much simpler when the language semantics are
deterministic.

> For example, a programmer often writes   i+j+k+l :: Integer 
> and does not care of many different ways in which this may evaluate.

Haskell specifies the order in such cases. It's ((i+j)+k)+l.

> For the case with  "if it's X ...",
> the way has to be chosen according to the
>   (1) type context,
>   (2) type expression specialization,
>   (3) priorities set.
> If the user omits the priorities, the compiler sets the defaults.
> What the recent implementations have, is (2).
> Sadly, this does not do in full the above example with  det.
> And I think, I could set the priorities for the above cases
> (C)...(FFP)  for  det.

I cannot think about an elegant solution which would allow specifying
selection of the best method automatically. Semantics of classes
are already complex without it. I certainly don't want to reach the
complexity of C++ templates, which are almost impossible to understand
fully in details and are a nightmare to implement correctly.

> > I feel such hierarchies unclear and would avoid them. [..]
> 
> It would be interesting to observe the examples showing this 
> unclearness. 
> Is not the example with  det  real-life and informative enough?
> What kind of ambiguity it may cause?

That neither I nor the compiler know for sure which version will be
used in each case, and how to specify another. Even the very basic
rule: in cases where the static type context determines a version, but
the actual argument given allows more specific one, which will be used.

> > I do not agree. If a generic implementation does not work for this
> > case, either it is not a special case of the generic case, or the
> > generic implementation is wrong itself. 
> > A generic implementation should work correctly for all special 
> > cases - that's why it is placed in a generic place and called 
> > generic.
> 
> A strange objection. The situation is that the generic implementation
> does work in the generic case and the special one works in the
> special case.

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.

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.

-- 
 __("<    Marcin Kowalczyk * [EMAIL PROTECTED] http://qrczak.ids.net.pl/
 \__/              GCS/M d- s+:-- a22 C+++$ UL++>++++$ P+++ L++>++++$ E-
  ^^                  W++ N+++ o? K? w(---) O? M- V? PS-- PE++ Y? PGP+ t
QRCZAK                  5? X- R tv-- b+>++ DI D- G+ e>++++ h! r--%>++ y-

Reply via email to