Redefining superclass default methods in a subclass

Hi, Looking at some of the ideas in http://www.haskell.org/haskellwiki/The_Other_Prelude , it struck me that the class system at the moment suffers from the problem that as hierarchies get deeper, the programmer is burdened more and more by the need to cut-and-paste method definitions between instances because Haskell doesn't allow a superclass (or ancestor class) method default to be redefined in a subclass. For example, consider this part of a proposal for Functor => Applicative => Monad: -- I've just used 'm' so it's easy to see what parts are relevant to Monad class Functor m where fmap :: (a -> b) -> m a -> m b class Functor m => Applicative m where return :: a -> m a (<*>) :: m (a -> b) -> m a -> m b (>>) :: m a -> m b -> m b ma >> mb = -- left as exercise for a rainy day! class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b The problem with this is that whereas someone defining a Monad at the moment only needs to define (return) and (>>=), with the above, though it gives obvious advantages in flexibility, generality etc, defining a new Monad involves providing methods (in instance decls) for fmap and (<*>) as well, and the default method for (>>) is ma >> mb = (fmap (const id) ma) <*> mb (from that page above) which I'm sure everyone will agree is a *lot* more complicated than: ma >> mb = ma >>= (\_ -> mb) Not only is the first definition for (>>) more complicated, it obscures the simple fact that for monads it's just a trivial special-use case of >>= where the bound argument is ignored. Therefore I'm wondering if it would be possible to allow default methods for a superclass to be defined, or redefined, in a subclass, so we could write: class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b mf <*> ma = mf >>= \f -> ma >>= \a -> return (f a) ma >> mb = ma >>= \_ = -> mb fmap f ma = ma >>= \a -> return (f a) (I know the above can be written in a more point-free style but I wrote it like that to make it easy to understand what's happening.) The essential point here (excuse the pun :-) ) is that it is impossible to write the default methods in the class in which the operation is defined, because the implementation depends on methods of the relevant subclass (and will therefore be different for different subclasses though not for each particular instance of a given ancestor class of a particular subclass). As Haskell stands at the moment, we are forced to cut and paste identical methods for each individual instance of each ancestor class of a particular subclass because we can't override an ancestor class method in the *class* decl for a subclass. The type class system at present is based on the idea that you can define related methods together and in terms of each other, at one level of the hierarchy. However as the above example shows, related methods sometimes need to be spread over the hierarchy but we still want to be able to define default implementations of them in terms of each other. Perhaps there is some reason this can't be done? Brian. -- http://www.metamilk.com

Brian Hulley wrote:
Hi, Looking at some of the ideas in http://www.haskell.org/haskellwiki/The_Other_Prelude , it struck me that the class system at the moment suffers from the problem that as hierarchies get deeper, the programmer is burdened more and more by the need to cut-and-paste method definitions between instances because Haskell doesn't allow a superclass (or ancestor class) method default to be redefined in a subclass.
The class aliases proposal lists several similar shortcomings of the current class system. http://repetae.net/john/recent/out/classalias.html
Perhaps there is some reason this can't be done?
Some random thoughts: How one would write instances? Using your Monad class, does instance Monad F where return = ... (>>=) = ... automatically define an instance for Applicative? If it does: What if there already is such an instance? Which one gets used for (>>)? The user-defined one or the Monad default? Is separate compilation still possible? (If there is no instance right now, one might pop out in another module...) If it does not: How can one define it, without copy-and-pasting the default? Zun.

Roberto Zunino wrote:
Brian Hulley wrote:
because Haskell doesn't allow a superclass (or ancestor class) method default to be redefined in a subclass.
How one would write instances? Using your Monad class, does instance Monad F where return = ... (>>=) = ... automatically define an instance for Applicative?
Yes, but I'd make the method names be "call-site-bound" so the actual method that is called is determined by the set of instance decls and class decls visible at each particular call site, and any instances that are automatically created would be hidden by any explicitly defined instances that are visible.
If it does: What if there already is such an instance? Which one gets used for (>>)? The user-defined one or the Monad default?
A possible proposal could be: 1) Class and instance decls would allow method implementations to be given for any methods in the class or any ancestor class. 2) Whenever an instance decl is visible there would always be a full set of instance decls for all ancestor classes, by supplementing the set of explicitly given instance decls that are visible by automatically generated implicit instance decls. 3) The most specific method implementation would always be chosen (ie prefer an explicit instance method over a class method and prefer a subclass method to a superclass method) In particular rule 2) would mean that the actual method used depends on what's available at the call site which means that a Haskell program could no longer be thought of as being re-written into a single module before compilation, since the meaning of overloaded names would be determined by (CalledFromModule, Type) not just Type. (The desire to hide instance decls or have different instance decls for the same type within the same program has come up before on the list but unfortunately I can't remember who posted something along these lines or when.)
Is separate compilation still possible? (If there is no instance right now, one might pop out in another module...)
I think it should still be possible because within a module, the overloadings would be determined by the set of explicitly defined instances in scope in that module. Various optimizations might be more tricky because the call site module associated with each overloaded name would need to be taken into account when inlining across module boundaries (ie a name used inside an inlined function needs to be resolved as if it had been used in the module where the function was defined not the module where the function is inlined). For example: module A where import Proposed.Control.Monad data T a = T a instance Monad T [-# INLINE foo #-} foo :: a -> T a foo = return -- uses Monad class default -- which is inherited from the Applicative -- class default module B where import A import Proposed.Control.Monad instance Applicative T where return x = retB x bar :: T a bar = return 'q' -- would use (retB) zap :: T a zap = foo 'q' -- would use (return) from A A question is whether the extra difficulty in resolving overloadings (for human reader as well as complier) is worth the advantages of being able to get generated definitions for (>>) for Applicative and (fmap) for Functor from a single instance decl for (Monad.>>=) etc. [Rearranged]
The class aliases proposal lists several similar shortcomings of the current class system. http://repetae.net/john/recent/out/classalias.html
IIUC the class alias proposal is about being able to group classes together and deal with them as a whole so similar issues of resolving overloadings arising from overlapping aliases/ explicit instance decls etc would arise (and I imagine the solutions would lie in similar directions). Brian. -- http://www.metamilk.com

Brian Hulley wrote:
...allow a superclass (or ancestor class) method default to be redefined in a subclass.
This has been proposed several times over the years. I remember seeing Simon PJ propose it within the past year or two, I think. I personally have needed this on several occasions. So I would be very happy if it were to be adopted. -Yitz

One of the great things about John's class-alias proposal is that John worked out a lot of details and captured them in a web page, rather than it getting buried in an email thread. If you have ideas to refine his proposal, it'd be good to see if, with him, you can come up with a unified design, and again capture it in a Wiki page or something. For myself, I have not fully grokked either proposal, nor gotten a feel for their cost-benefit ratio. But the more well-articulated a design is, the more likely it is for someone (at GHC HQ or elsewhere) to have a go at implementing it. And of course one or two well-refined proposals are more attractive to implement than half a dozen intriguing but overlapping ones. So this message is really to encourage you to collaborate on writing up type-system proposals, at least ones that you care about. Simon | -----Original Message----- | From: haskell-cafe-bounces@haskell.org [mailto:haskell-cafe-bounces@haskell.org] On Behalf Of Roberto | Zunino | Sent: 04 January 2007 22:44 | To: Brian Hulley | Cc: Haskell-cafe | Subject: Re: [Haskell-cafe] Redefining superclass default methods in a subclass | | Brian Hulley wrote: | > Hi, | > Looking at some of the ideas in | > http://www.haskell.org/haskellwiki/The_Other_Prelude , it struck me that | > the class system at the moment suffers from the problem that as | > hierarchies get deeper, the programmer is burdened more and more by the | > need to cut-and-paste method definitions between instances because | > Haskell doesn't allow a superclass (or ancestor class) method default to | > be redefined in a subclass. | | The class aliases proposal lists several similar shortcomings of the | current class system. | | http://repetae.net/john/recent/out/classalias.html | | > Perhaps there is some reason this can't be done? | | Some random thoughts: | | How one would write instances? Using your Monad class, does | instance Monad F where | return = ... | (>>=) = ... | automatically define an instance for Applicative? | | If it does: What if there already is such an instance? Which one gets | used for (>>)? The user-defined one or the Monad default? Is separate | compilation still possible? (If there is no instance right now, one | might pop out in another module...) | | If it does not: How can one define it, without copy-and-pasting the default? | | Zun. | _______________________________________________ | Haskell-Cafe mailing list | Haskell-Cafe@haskell.org | http://www.haskell.org/mailman/listinfo/haskell-cafe

Simon Peyton-Jones wrote:
One of the great things about John's class-alias proposal is that John worked out a lot of details and captured them in a web page, rather than it getting buried in an email thread.
If you have ideas to refine his proposal, it'd be good to see if, with him, you can come up with a unified design, and again capture it in a Wiki page or something.
I've started a page at http://www.haskell.org/haskellwiki/Class_system_extension_proposal John - I haven't added anything about your class alias proposal because you've copyrighted your proposal and I don't want to do the wrong thing ;-) Can you add a section for your proposal (or containing a link to your proposal) on his page? Please could everyone who has any other ideas about class system extensions add them to the page above so that we have a central location. Hopefully it will gradually become clear what all the issues are and perhaps a path for trying to implement some extensions in order of difficulty. Thanks, Brian. -- http://www.metamilk.com

Brian Hulley wrote:
Simon Peyton-Jones wrote:
One of the great things about John's class-alias proposal is that John worked out a lot of details and captured them in a web page, rather than it getting buried in an email thread.
If you have ideas to refine his proposal, it'd be good to see if, with him, you can come up with a unified design, and again capture it in a Wiki page or something.
I've started a page at http://www.haskell.org/haskellwiki/Class_system_extension_proposal John - I haven't added anything about your class alias proposal because you've copyrighted your proposal and I don't want to do the wrong thing ;-) Can you add a section for your proposal (or containing a link to your proposal) on his page? ^^^ Apologies for typo - should be "this page"
Best regards, Brian. -- http://www.metamilk.com

Hi Brian, Thank you for starting the thread. We (Martin Sulzmann and me) proposed a type class extension which allows modular extension of superclasses (a complement of subclass extension). The idea has been shown to be particularly useful in (but not limited to) encodings of generic programming with type classes. A brief introduction of the proposal is documented in our wiki: http://taichi.ddns.comp.nus.edu.sg/taichiwiki/GPHomePage. I will try to add it as a link in yours soon. A more detailed and formal description of the proposal can be found in our Workshop on Generic Programming 06 paper which is available at http://www.comp.nus.edu.sg/~sulzmann http://web.comlab.ox.ac.uk/oucl/work/meng.wang/ Regards, Meng -W-M- @ @ | \_/ On Tue, 9 Jan 2007, Brian Hulley wrote:
Simon Peyton-Jones wrote:
One of the great things about John's class-alias proposal is that John worked out a lot of details and captured them in a web page, rather than it getting buried in an email thread.
If you have ideas to refine his proposal, it'd be good to see if, with him, you can come up with a unified design, and again capture it in a Wiki page or something.
I've started a page at http://www.haskell.org/haskellwiki/Class_system_extension_proposal John - I haven't added anything about your class alias proposal because you've copyrighted your proposal and I don't want to do the wrong thing ;-) Can you add a section for your proposal (or containing a link to your proposal) on his page?
Please could everyone who has any other ideas about class system extensions add them to the page above so that we have a central location. Hopefully it will gradually become clear what all the issues are and perhaps a path for trying to implement some extensions in order of difficulty.
Thanks, Brian. -- http://www.metamilk.com _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Meng Wang wrote:
Hi Brian,
Thank you for starting the thread. We (Martin Sulzmann and me) proposed a type class extension which allows modular extension of superclasses (a complement of subclass extension). The idea has been shown to be particularly useful in (but not limited to) encodings of generic programming with type classes. A brief introduction of the proposal is documented in our wiki: http://taichi.ddns.comp.nus.edu.sg/taichiwiki/GPHomePage. I will try to add it as a link in yours soon.
A more detailed and formal description of the proposal can be found in our Workshop on Generic Programming 06 paper which is available at http://www.comp.nus.edu.sg/~sulzmann http://web.comlab.ox.ac.uk/oucl/work/meng.wang/
Thanks - I've added the above links to http://www.haskell.org/haskellwiki/Class_system_extension_proposal along with a one-line summary which I'm sure is totally imperfect but hopefully not too misleading ;-) Best regards, Brian. -- http://www.metamilk.com

Hello Brian, Thursday, January 4, 2007, 10:00:05 PM, you wrote:
deeper, the programmer is burdened more and more by the need to cut-and-paste method definitions between instances because Haskell doesn't allow a superclass (or ancestor class) method default to be redefined in a subclass.
i've runned into this problem with Streams library. finally i've decided to wrote bodies of such methods outside of class: getLineBody :: (CharStream h) => h -> IO String getLineBody h = do c <- getChar .... instance LineStream File where getLine = getLineBody instance LineStream MemBuf where getLine = getLineBody where File and MemBuf, of course, are CharStream instances -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Bulat Ziganshin wrote:
Hello Brian,
Thursday, January 4, 2007, 10:00:05 PM, you wrote:
deeper, the programmer is burdened more and more by the need to cut-and-paste method definitions between instances because Haskell doesn't allow a superclass (or ancestor class) method default to be redefined in a subclass.
i've runned into this problem with Streams library. finally i've decided to wrote bodies of such methods outside of class: [example snipped]
Hello Bulat, Thanks for the workaround, which solves the need to copy and paste method bodies though not the problem of having to write out instance decls for a potentially large chain of classes leading to the subclass of interest. Part of the motivation for proposing that a superclass method default could be redefined in a subclass (or a particular instance) is that it would allow some refactorings of the class hierarchy without affecting code that just uses the subclass - in particular it would allow existing code using Monad to compile unchanged even when Monad moved down to Functor => Applicative => Monad because return and >>= are enough to get completely defined instances for Functor and Applicative. Best regards, Brian. -- http://www.metamilk.com
participants (6)
-
Brian Hulley
-
Bulat Ziganshin
-
Meng Wang
-
Roberto Zunino
-
Simon Peyton-Jones
-
Yitzchak Gale