Scalable and Continuous
Fri, 16 Feb 2001 22:21:57 -0600
Marcin 'Qrczak' Kowalczyk wrote:
> Wed, 14 Feb 2001 23:27:55 -0600, Matt Harden <firstname.lastname@example.org> pisze:
> > I also wonder: should one be allowed to create new superclasses of an
> > existing class without updating the original class's definition?
> It would not buy anything. You could not make use of the superclass
> in default definitions anyway (because they are already written).
But that's not the point. The point is you could create objects that
were only instances of the new superclass and not of the subclass. It
allows us to have hidden superclasses of Num that wouldn't even have to
be referenced in the standard Prelude, for instance. It allows users to
define (+) for a type without defining (*), by creating an appropriate
superclass of Num. We could keep the current Prelude while allowing
numerous "Geek Preludes" that could coexist with the std one (at least
with regard to this particular issue).
> And what would happen to types which are instances of the subclass
> but not of the new superclass?
They would automatically be instances of the new superclass. Why not?
They already have all the appropriate functions defined. Again, I
wouldn't allow default definitions for the same function in multiple
classes, and this is one of the reasons. It would introduce ambiguity
when a type that is an instance of a subclass, and didn't override the
default, was considered as an instance of the superclass.
> > Also, should the subclass be able to create new default definitions
> > for functions in the superclasses?
> I hope the system can be designed such that it can.
Me too :).
> > such defaults would only be legal if the superclass did not define
> > a default for the same function.
> Not necessarily. For example (^) in Num (of the revised Prelude)
> has a default definition, but Fractional gives the opportunity to
> have better (^) defined in terms of other methods. When a type is an
> instance of Fractional, it should always have the Fractional's (^)
> in practice. When not, Num's (^) is always appropriate.
> I had many cases like this when trying to design a container class
> system. It's typical that a more specialized class has something
> generic as a superclass, and that a more generic function can easily
> be expressed in terms of specialized functions (but not vice versa).
> It follows that many kinds of types have the same written definition
> for a method, which cannot be put in the default definition in the
> class because it needs a more specialized context.
> It would be very convenient to be able to do that, but it cannot be
> very clear design. It relies on the absence of an instance, a negative
> constraint. Hopefully it will be OK, since it's determined once for a
> type - it's not a systematic way of parametrizing code over negative
> constrained types, which would break the principle that additional
> instances are harmless to old code.
What happens if classes A and B are superclasses of C, all three
define a default for function foo, and we have a type that's an instance
of A and B, but not C, which doesn't override foo? Which default do we
use? It's not only a problem for the compiler to figure out, it also
quickly becomes confusing to the programmer. I'd rather just make the
simple rule of a single default per function. If multiple "standard"
definitions for a function make sense, then be explicit about which one
you want for each type; i.e.:
instance Fractional MyFraction where
(^) = fractionalPow
> This design does have some problems. For example what if there are two
> subclasses which define the default method in an incompatible ways.
> We should design the system such that adding a non-conflicting instance
> does not break previously written code. It must be resolved once per
> module, probably complaining about the ambiguity (ugh!), but once
> the instance is generated, it's cast in stone for this type.
Yeah, ugh. I hate having opportunities for ambiguity. Simple rules and
obvious results are far better, IMHO.
> > What do you mean by mutual definitions?
(snipped explanation of mutual definitions)
OK, that's what I thought :). I didn't really think this was of
particular importance with allowing the definition of superclass's
instances in subclasses, but now I think I see why you said that. It
would be easy to forget to define one of the functions if the defaults
are way up the hierarchy in one of the superclasses.
Btw, I'm one of those who agrees that omitting a definition of a class
function in an instance should be an error. If you really intend to
omit the implementation of a function without a default, define it as
(error "Intentionally omitted")!