
On Fri, Apr 25, 2008 at 05:37:17PM +0100, Simon Peyton-Jones wrote:
John
OK here's a question about class alisas. You propose:
class Foo a where foo :: a -> [a] foo x = [] class Bar a where bar :: a -> a bar x = [x]
class alias FooBar a = (Foo a, Bar a) where foobar :: a -> a foobar x = x
foo x = bar x
I have a few minor questions about this that'd be worth clarifying on your main page (a) I assume you can add a method 'foobar' not declared in either Foo or Bar. Your very first example has this. But it's contradicted later when you say that "One can declare an instance of Num either by giving separate instances for Eq, Additive, Multiplicative"
No, I didn't mean to imply that. as you noted, that would mean that an alias would not be a simple preprocessing. what you can do is create a real class FooBar which has both foo and bar as superclasses, then create an alias for all three so you can create a single instance to define all three. It might be possible to add this though and have it automatically create an appropriate class and a class alias. but I think that might muddy the intent of class aliases if not all can be described as simple aliases for other existing classes. So we can reserve that as a possible future extension. it is easy enough to manually create the 'foobar' containing class in any case and if you don't export it from your module, you get the equivalent effect.
(b) And I assume that you don't need to repeat the type signatures for 'foo' and 'bar'.
Yup, no need to repeat type signatures.
(c) I think you intend that you can override the default methods for foo and bar; and I have done so for method 'foo'.
Yes. being able to override default instances is a key part, otherwise you wouldn't be able to, for instance, create appropriate 'Num' compatible methods from your advanced 'NewNum' class alias. But you are not forced to do so.
Question: how does the above differ from this?
class (Foo a, Bar a) => FooBarSC a where foobar :: a -> a
Here Foo, Bar are simply superclasses. From the point of view of a type signature there *no* difference:
f :: (FooBarSC a) => ...
gives access to all the methods of Foo and Bar. So what's the difference?
Answer (I believe): when you give an instance of FooBar you give implementations for all methods of Foo, Bar, and FooBar.
Yes, it is because you cannot declare an instance of FooBarSC and have it also create the ones for Foo and Bar, and because you can't create new default instances for 'foo' and 'bar' that refer to 'foobar'.
So the obvious question is: do we really need a new construct? Why not just use FooBarSC? Then we'd have to allow you to give implementations for superclass methods too: instance FooBarSC Int where foobar = ... foo = ... bar = ...
I think I believe (like you) that this is a bad idea. The main reason is that it's a totally unclear whether, given a FooBarSC Int instance declaration, should there be an instance for (Foo Int), always, never, or optionally?
Yes. and it breaks one of the major haskell properties I am not willing to give up, That when you add an import to your module (or any module you depend on), your program will either fail to compile or have the _exact_ same meaning.
However, I think you might want to articulate the reasons carefully, because we have two features that are really extremely close.
Yes, I think the above might help with the lattice case, where you have a strict hierarchy of classes and the superclass tree mirrors your class aliases, but this may not be the case in general. in particular, we don't want to necessarily redo the numeric hierarchy as a strict generalization or splitting up of the current prelude numerical hierarchy, and the superclass method gets a little hairy when you want to do things like that and don't want to end up with every method in its own class.
To put it another way, you could imagine re-expressing your proposal like this:
class (Eq a) && (Additive a, Multiplicative a) => Num a
meaning this: when you give an instance for (FooBar T) you
* MUST give implementations for the methods of Addititive and Applicative
* MUST NOT give implementations for methods of Eq; rather the Eq T instance must be in scope.
This is, I believe, what you mean by class alias Num a = Eq a => (Additive a, Multiplicative a)
Now I'm not necessarily suggesting this as concrete syntax. But my point is that you're really asking for small modification of the existing superclass mechanism, that divides the superclasses into two groups, the "flat" ones (like Additive and Multiplicative) and the "nested" ones (like Eq). Is that right? If so, a syntax that is more suggestive of the current superclass declaration looks better to me.
Hmm.. I consider class aliases orthogonal and complementary to superclasses, rather than a generalization of them. I do not think that what one wants out of a superclass hierarchy and a class alias collection (note, I don't say hierarchy) will be the same and having them mirror each other will create a tension between the two in how one designs their code.
This close relationship also suggests strongly that the answer to (a) above should be 'yes', since you can certainly add methods to a class with superclasses.
Yeah, I disagree here, mainly because I don't want to conflate superclasses with class aliases. I feel they have different uses, even though they can sometimes achieve the same thing. Part of this is my 'sather' heritige, sather was a pretty cool fully type safe (in the stronger FP sense moreso than the java sense) object oriented language that was somewhat based on eiffel, but with the advantage of being designed by people with a stronger grasp of type theory than the eiffel designer. One of its major innovations was a separation of code reuse from class inheritance. In traditional object oriented languages, inheritance is both a method of code reuse (by defining non abstract superclasses with virtual methods that can be overridden), and of expressing a strong relationship between the types. However the sather designers realized these two things were not necessarily related, their solution was to define superclasses as _always_ being abstract, and having a different mechanism for code reuse. It was quite nice. enough so that I started (not as elegantly) using that approach in other OO languages I was using. Had I not discovered Haskell, I would likely still be the sather language maintainer :) I believe that class aliases define a method of code (instance method in particular) reuse. When you define your 'HaskellPrimePrelude' instances, you want the code to automatically and transparently be used to define 'Haskell98Prelude' instances, likewise, when you define 'Haskell98Prelude' instances, you want the code to automatically and transparently define 'HaskellPrimePrelude' instances. However, there is no clear superclass relationship between the two and being forced to introduce an artificial one would obfuscate things I think. John -- John Meacham - ⑆repetae.net⑆john⑈