
A brief idea: something like... -- class (Additive a) => Scalable a scale :: Real -> a -> a -- equivalent to * (not sure of name for Real type) class (Scalable b) => Continuous a b | a -> b add :: b -> a -> a difference :: a -> a -> b -- Vectors, for instance, are Scalable. You can multiply them by any real number to get another vector. Num would also be Scalable. An example of Continuous would be time, e.g. "Continuous Time Interval". There's no zero time, although there is a zero interval. Space too: "Continuous Position Displacement", since there's no "zero position". -- Ashley Yakeley, Seattle WA

On Mon, 12 Feb 2001, Ashley Yakeley wrote:
class (Additive a) => Scalable a scale :: Real -> a -> a -- equivalent to * (not sure of name for Real type)
Or times, which would require multiparameter classes. 5 `times` "--" == "----------" 5 `times` (\x -> x+1) === (\x -> x+5) But this would suggest separating out Monoid from Additive - ugh. It makes sense to have zero and (+) for lists and functions a->a, but not negation. There is a class Monoid for ghc's nonstandard MonadWriter class. We would have (++) unified with (+) and concat unified with sum. I'm afraid of making too many small classes. But it would perhaps be not so bad if one could define superclass' methods in subclasses, so that one can forget about exact structure of classes and treat a bunch of classes as a single class if he wishes. It would have to be combined with compiler-inferred warnings about mutual definitions giving bottoms. -- Marcin 'Qrczak' Kowalczyk

Marcin 'Qrczak' Kowalczyk wrote:
I'm afraid of making too many small classes. But it would perhaps be not so bad if one could define superclass' methods in subclasses, so that one can forget about exact structure of classes and treat a bunch of classes as a single class if he wishes. It would have to be combined with compiler-inferred warnings about mutual definitions giving bottoms.
I totally agree with this. We should be able to split up Num into many superclasses, while still retaining the traditional Num, and not inconveniencing anybody currently using Num. We could even put the superclasses into Library modules, so as not to "pollute" the standard Prelude's namespace. The Prelude could import those modules, then define Num and Num's instances, and only export the Num stuff. We shouldn't have to be afraid of making too many classes, if that more precisely reflects reality. It is only the current language definition that makes us afraid of this. We should be able to work with a class, subclass it, and define instances of it, without needing to know about all of its superclasses. This is certainly true in OOP, although I realize of course that OOP classes are not Haskell classes. I also wonder: should one be allowed to create new superclasses of an existing class without updating the original class's definition? Also, should the subclass be able to create new default definitions for functions in the superclasses? I think it should; such defaults would only be legal if the superclass did not define a default for the same function. What do you mean by mutual definitions? Matt Harden

Wed, 14 Feb 2001 23:27:55 -0600, Matt Harden
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). And what would happen to types which are instances of the subclass but not of the new 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.
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. 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.
What do you mean by mutual definitions?
Definitions of methods in terms of each other. Suppose there is a class having only (-) and negate, with default definitions: a - b = a + negate b negate b = zero - b When we make an instance of its subclass but don't make an explicit instance of this class and don't write (-) or negate explicitly, it would be dangerous if the compiler silently included definitions generated by the above, because both are functions which always return bottoms. The best solution I can think of is to let the compiler deduce that these default definitions lead to a useless instance, and give a warning when both are instantiated from the default. It cannot be an error because there is no formal way we can distinguish bad mutual recursion from good mutual recursion. The validity of the code cannot depend on heuristics, but warnings can. There are already warnings when a method without default is not defined explicitly (although people say it should be an error; it is diagnosable). -- __("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/ \__/ ^^ SYGNATURA ZASTÊPCZA QRCZAK

Marcin 'Qrczak' Kowalczyk wrote:
Wed, 14 Feb 2001 23:27:55 -0600, Matt Harden
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")! Matt Harden

On Fri, Feb 16, 2001 at 10:21:57PM -0600, Matt Harden wrote:
Marcin 'Qrczak' Kowalczyk wrote:
Wed, 14 Feb 2001 23:27:55 -0600, Matt Harden
pisze: 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. 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.
(Presumably you mean that A and B are subclasses of C, which contains foo.) I would make this an error, easily found by the compiler. But I need to think more to come up with well-defined and uniform semantics.
.. 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 is another option. It has the advantage of being explicit and allowing you to choose easily in cases of ambiguity. It is more conservative, but possibly less convenient. Best, Dylan Thurston

Dylan Thurston wrote:
On Fri, Feb 16, 2001 at 10:21:57PM -0600, Matt Harden wrote:
Marcin 'Qrczak' Kowalczyk wrote:
Wed, 14 Feb 2001 23:27:55 -0600, Matt Harden
pisze: 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. 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.
(Presumably you mean that A and B are subclasses of C, which contains foo.) I would make this an error, easily found by the compiler. But I need to think more to come up with well-defined and uniform semantics.
No, I meant superclasses. I was referring to the possible feature we (Marcin and I) were discussing, which was the ability to create new superclasses of existing classes. If you are allowed to create superclasses which are not referenced in the definition of the subclass, then presumably you could create two classes A and B that contained foo from C. You would have to then be able to create a new subclass of both of those classes, since C is already a subclass of both. Then the question becomes, if they both have a default for foo, who wins? My contention was that the compiler should not allow a default for foo in the superclass and the subclass because that would introduce ambiguities. I would now like to change my stance on that, and say that defaults in the superclasses could be allowed, and in a class AB subclassing both A and B, there would be no default for foo unless it was defined in AB itself. Also C would not inherit any default from A or B, since it does not mention A or B in its definition. If this feature of creating new superclasses were adopted, I would also want a way to refer explicitly to default functions in a particular class definition, so that one could say that foo in AB = foo from A. BTW, I'm not saying this stuff is necessarily a good idea, just exploring the possibility. Matt Harden
participants (5)
-
Ashley Yakeley
-
Dylan Thurston
-
Marcin 'Qrczak' Kowalczyk
-
Matt Harden
-
qrczak@knm.org.pl