[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

More flexible inheritance

Trans

2/18/2007 1:25:00 AM

A notion came to me yesterday with regards to how we extend classes.
On the one hand we can create a module and include it into a class,
but this will not allow us to override a preexisting method -- for
that we must reopen the class and play the alias and/ or binding game.
Various ideas have been put forth for simplifying and improving the
robustness of this, from #wrap_method, alias_method_chain, as well
as :pre, :post methods and my own cuts (albeit they go a bit further
than just method wrapping). But occurs to me that these add
complexities onto something that is already straightforward if we are
willing to separate out all class concerns into modules. In other
words instead of writing:

class X
def a; "a"; end
end

write:

module Xa
def a; "a"; end
end

class X
include Xa
end

X.new.a #=> "a"

Then it's easy enough to overlay new behaviors:

module Xb
def a; "{"+super+"}"; end
end

class X
include Xb
end

X.new.a #=> "{a}"

Since that works so well, it occurs to me, why not make this the
fundamental behavior of defining a class? In other words defining a
class:

class X
def a; "a"; end
end

could be equivalent to writing:

class X
include( Module.new{
def a; "a"; end
} )
end

So there would always be a module involved in the definition of a
class. And classes become simply containers of modules.

T.


12 Answers

SonOfLilit

2/18/2007 12:10:00 PM

0

Wouldn't this hurt performance a lot? For each and every message
passed, one more step in lookup.

I don't know.

Aur Saraf

On 2/18/07, Trans <transfire@gmail.com> wrote:
> A notion came to me yesterday with regards to how we extend classes.
> On the one hand we can create a module and include it into a class,
> but this will not allow us to override a preexisting method -- for
> that we must reopen the class and play the alias and/ or binding game.
> Various ideas have been put forth for simplifying and improving the
> robustness of this, from #wrap_method, alias_method_chain, as well
> as :pre, :post methods and my own cuts (albeit they go a bit further
> than just method wrapping). But occurs to me that these add
> complexities onto something that is already straightforward if we are
> willing to separate out all class concerns into modules. In other
> words instead of writing:
>
> class X
> def a; "a"; end
> end
>
> write:
>
> module Xa
> def a; "a"; end
> end
>
> class X
> include Xa
> end
>
> X.new.a #=> "a"
>
> Then it's easy enough to overlay new behaviors:
>
> module Xb
> def a; "{"+super+"}"; end
> end
>
> class X
> include Xb
> end
>
> X.new.a #=> "{a}"
>
> Since that works so well, it occurs to me, why not make this the
> fundamental behavior of defining a class? In other words defining a
> class:
>
> class X
> def a; "a"; end
> end
>
> could be equivalent to writing:
>
> class X
> include( Module.new{
> def a; "a"; end
> } )
> end
>
> So there would always be a module involved in the definition of a
> class. And classes become simply containers of modules.
>
> T.
>
>
>

SonOfLilit

2/18/2007 12:12:00 PM

0

Hey, how about a new #include variant that does alias magic whenever a
method exists in both the class and the mixin module?

Aur Saraf

On 2/18/07, SonOfLilit <sonoflilit@gmail.com> wrote:
> Wouldn't this hurt performance a lot? For each and every message
> passed, one more step in lookup.
>
> I don't know.
>
> Aur Saraf
>
> On 2/18/07, Trans <transfire@gmail.com> wrote:
> > A notion came to me yesterday with regards to how we extend classes.
> > On the one hand we can create a module and include it into a class,
> > but this will not allow us to override a preexisting method -- for
> > that we must reopen the class and play the alias and/ or binding game.
> > Various ideas have been put forth for simplifying and improving the
> > robustness of this, from #wrap_method, alias_method_chain, as well
> > as :pre, :post methods and my own cuts (albeit they go a bit further
> > than just method wrapping). But occurs to me that these add
> > complexities onto something that is already straightforward if we are
> > willing to separate out all class concerns into modules. In other
> > words instead of writing:
> >
> > class X
> > def a; "a"; end
> > end
> >
> > write:
> >
> > module Xa
> > def a; "a"; end
> > end
> >
> > class X
> > include Xa
> > end
> >
> > X.new.a #=> "a"
> >
> > Then it's easy enough to overlay new behaviors:
> >
> > module Xb
> > def a; "{"+super+"}"; end
> > end
> >
> > class X
> > include Xb
> > end
> >
> > X.new.a #=> "{a}"
> >
> > Since that works so well, it occurs to me, why not make this the
> > fundamental behavior of defining a class? In other words defining a
> > class:
> >
> > class X
> > def a; "a"; end
> > end
> >
> > could be equivalent to writing:
> >
> > class X
> > include( Module.new{
> > def a; "a"; end
> > } )
> > end
> >
> > So there would always be a module involved in the definition of a
> > class. And classes become simply containers of modules.
> >
> > T.
> >
> >
> >
>

George

2/18/2007 12:37:00 PM

0

On 2/18/07, Trans <transfire@gmail.com> wrote:
> Since that works so well, it occurs to me, why not make this the
> fundamental behavior of defining a class? In other words defining a
> class:
>
> class X
> def a; "a"; end
> end
>
> could be equivalent to writing:
>
> class X
> include( Module.new{
> def a; "a"; end
> } )
> end
>
> So there would always be a module involved in the definition of a
> class. And classes become simply containers of modules.
>
> T.

Hi Tom,

The problem to my eyes is that they're not equivalent.

The former is still required when you want to redefine an existing
method in a class, often one you didn't write yourself. If you change
the former to mean the latter, does that mean we can't redefine
methods like that?

Regards,
George.

Pit Capitain

2/18/2007 4:18:00 PM

0

George Ogata schrieb:
> The problem to my eyes is that they're not equivalent.
>
> The former is still required when you want to redefine an existing
> method in a class, often one you didn't write yourself. If you change
> the former to mean the latter, does that mean we can't redefine
> methods like that?

George, I'm not Tom, but I think that for redefining a method, instead of

class X
def a; "a2"; end
end

internally you would do something like

X.ancestors[ 1 ].module_eval {
def a; "a2"; end
}

Everything you would do with class X in normal Ruby you would do to the
anonymous module being the first in the ancestors chain.

Regards,
Pit

George

2/18/2007 5:26:00 PM

0

On 2/19/07, Pit Capitain <pit@capitain.de> wrote:
> George, I'm not Tom, but I think that for redefining a method, instead of
>
> class X
> def a; "a2"; end
> end
>
> internally you would do something like
>
> X.ancestors[ 1 ].module_eval {
> def a; "a2"; end
> }
>
> Everything you would do with class X in normal Ruby you would do to the
> anonymous module being the first in the ancestors chain.

Hi Pit,

Ah ok, I was overlooking the fact that *original* methods of X would
live in an anonymous module as well.

It's an interesting idea. It does still change the existing semantics
a little, though. Currently, if you do:

class C
def f; 'C'; end
end

module M
def f; 'M'; end
end

C.send(:include, M)

...C still comes first in the lookup path. You'd need to ensure that
the anonymous modules (eigenmodules? ;-) come at the front of the
ancestor chain if you wanted to preserve this behavior.

But yeah, interesting idea!

Regards,
George.

George

2/18/2007 5:58:00 PM

0

On 2/18/07, SonOfLilit <sonoflilit@gmail.com> wrote:
> Wouldn't this hurt performance a lot? For each and every message
> passed, one more step in lookup.
>
> I don't know.
>
> Aur Saraf

Hi Aur,

I believe the result of method lookups are cached, so you mightn't get
burnt too bad.

Alternatively, if we're only trying to affect method lookup, you could
keep storing methods in the class, but store a list of method bodies
for each name rather than just one, and only change super to look
beyond the front of the list if necessary.

Maybe Trans had a grander vision using full Module objects, though.

Regards,
George.

Gary Wright

2/18/2007 7:49:00 PM

0


On Feb 17, 2007, at 8:24 PM, Trans wrote:

> A notion came to me yesterday with regards to how we extend classes.
> On the one hand we can create a module and include it into a class,
> but this will not allow us to override a preexisting method -- for
> that we must reopen the class and play the alias and/ or binding game.

Why not just use a subclass? Isn't that one of the primary motivations
for inheritance in the first place, to override a method in an ancestor?

Gary Wright




Jörg W Mittag

2/18/2007 9:59:00 PM

0

Trans wrote:
> [...] But occurs to me that these add
> complexities onto something that is already straightforward if we are
> willing to separate out all class concerns into modules.

Okay, I must admit that I actually don't have the slightest idea about
Traits[1], but from what little I heard about them, this sounds a lot
like it. Doesn't it?

And I read some blog entry somewhere by somebody who wanted to
implement Traits in Ruby but AFAIR there was no code (yet).

jwm

[1] <http://www.iam.unibe.ch/~scg/Research/T...

Trans

2/19/2007 2:49:00 AM

0



On Feb 17, 8:40 pm, "Andrew Stone" <stoneli...@gmail.com> wrote:
> Trans,
>
> I'm actually in the middle of a week long argument concerning "fat models"
> versus "thin models" and with my approach being fat libraries and thin
> models.

That's a long argument ;)

> We have a few sites which will share a lot of functionality and
> instead of sharing the models, I like sharing libraries that are included in
> the models. The idea being that this is easy to do with svn externals on
> the lib directory. Then fix a bug or add functionality in one place. Even
> though this gives two places to look for methods, I think it's nice and
> clean.
>
> Even though my example is for a specific implementation, the idea of classes
> becoming containers of modules is a great solution to sharing functionality.

I think yours is a perfect example. Modules provide SOC and
consequently allow one to more easily mix and match aspects to
different models. That a class would become exclusively a container of
modules (anonymous or explicit) at the very least helps promote a good
practice.

The only substantial argument against such an approach I've come up is
one in preference of delegation over inheritance. If available aspects
are modules rather than classes then we are sort-of pushed toward
using inheritance, even though in some cases delegation would be
preferable. However it's easy enough to maneuver around this if need
be, for example:

class Module
def new(*a,&b)
base = self
Class.new{ include base }.new(*a,&b)
end
end

> I'm pleased to hear others are thinking this way.

Me too.

T.


Trans

2/19/2007 2:58:00 AM

0



On Feb 18, 12:26 pm, "George Ogata" <george.og...@gmail.com> wrote:
> On 2/19/07, Pit Capitain <p...@capitain.de> wrote:
>
> > George, I'm not Tom, but I think that for redefining a method, instead of
>
> > class X
> > def a; "a2"; end
> > end
>
> > internally you would do something like
>
> > X.ancestors[ 1 ].module_eval {
> > def a; "a2"; end
> > }
>
> > Everything you would do with class X in normal Ruby you would do to the
> > anonymous module being the first in the ancestors chain.
>
> Hi Pit,
>
> Ah ok, I was overlooking the fact that *original* methods of X would
> live in an anonymous module as well.
>
> It's an interesting idea. It does still change the existing semantics
> a little, though. Currently, if you do:
>
> class C
> def f; 'C'; end
> end
>
> module M
> def f; 'M'; end
> end
>
> C.send(:include, M)
>
> ...C still comes first in the lookup path. You'd need to ensure that
> the anonymous modules (eigenmodules? ;-) come at the front of the
> ancestor chain if you wanted to preserve this behavior.

Actually, one of the benefits is that they would not come first,
becuase then you can override methods without resorting to alias and
what have you. However there's no reason we could not have the ability
to do both (which as you point out would benefit backward
compatability.) In fact as Pit points out we could have quite a bit of
flexibility accessing the module stack.

T.