Haskell' - class aliases

John Meacham john at repetae.net
Fri Apr 25 19:45:55 EDT 2008


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⑈


More information about the Haskell-prime mailing list