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.
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. 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.
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.
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.
···
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.
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
···
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.
[...] 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).
I am not sure if I am grasping the quintessence (I have not forgotten
of your post
but your ideas just reinforce an idea which is getting clearer and
clearer in my mind.
Use classes with care, actually we could do some Javascript in pure
ruby, kindly look at this
code e.g.
The variation to your idea is that I really forget Class and only use
objects and prototypes.
So #include becomes #extend, Class.new becomes a constructor in the
Javascript sense.
module M1
attr_accessor :name
end
module M2
attr_reader :value
def inc; @value += 1; end
def dec; @value -=1 ; end
def init @value = 41
self
end
end
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.
--
We have not succeeded in answering all of our questions.
In fact, in some ways, we are more confused than ever.
But we feel we are confused on a higher level and about more important things.
-Anonymous
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.
>
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.
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.
···
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'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.
···
On Feb 17, 8:40 pm, "Andrew Stone" <stoneli...@gmail.com> wrote:
You're right of course --if that's what one needs. But often one wants
to extend a prexisting class. There are numerous reasons one might
want to do that, and which arn't very suitable to subclassing.
Imagine if we didn't have open classes , then just to add a single
method to a class required a new subclass. (would Ruby start to look a
lot more like Java?)
T.
···
On Feb 18, 2:49 pm, Gary Wright <gwtm...@mac.com> wrote:
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?
The benifits are more robust means of extension b/c
1) mixins can be used for extensions
2) mixins are much more flexible and elegant than open/alias/
redefine
3) no longer need to fuss with alias names or method binding
4) we would no longer require alias (which is a keyword)
5) lends itself to future possiblities such as uninclude
6) promotes better design and way of thinking whereby
a class is a collection of "bahaviors" (sub your prefered term)
Besides that this change would make remove_method impossible.
remove_method would be possible. by default it could effect the
"lowest" module. and if need be, of course, one can select the
particular mixin to effect.
I vaguely think of similar idea with combination of class box, so that
we can control side-effect from open classes, i.e.
namespace foo # fake syntax
# re-opening class that effective only in this namespace
class String
def a; "a"; end
end
"foo".a # "a" method available here
But I am still not confident of the benefit and effect of this
behavior.
another topic worth consideration, but selector namespaces are a
different animal I think --well, unless we can dynamically activate
and deactive the mixin modules depedning on the namespace we're in,
then namespaces could be based on these. Hmm...that's a cool idea, but
I suspect it would require a major overhaul in how Ruby works under
the hood.
T.
···
On Feb 19, 3:04 am, Yukihiro Matsumoto <m...@ruby-lang.org> wrote:
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.
···
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.
The benifits are more robust means of extension b/c
1) mixins can be used for extensions
2) mixins are much more flexible and elegant than open/alias/redefine
3) no longer need to fuss with alias names or method binding
4) we would no longer require alias (which is a keyword)
5) lends itself to future possiblities such as uninclude
6) promotes better design and way of thinking whereby
a class is a collection of "bahaviors" (sub your prefered term)
I think I get the picture .. gradually. Interesting.
Can you be more specific about the suggestion, for example, for the
code
class Foo < Object
def foo
end
end
class Foo
def bar
end
end
what would happen? Can we access appending anonymous mixin module?
another topic worth consideration, but selector namespaces are a
different animal I think --well, unless we can dynamically activate
and deactive the mixin modules depedning on the namespace we're in,
then namespaces could be based on these. Hmm...that's a cool idea, but
I suspect it would require a major overhaul in how Ruby works under
the hood.
Sure it is. But I suspect this proposal is nearly as huge as selector
namespace for language change, although implementation is far easier.
matz.
···
In message "Re: More flexible inheritance" on Thu, 22 Feb 2007 02:43:14 +0900, "Trans" <transfire@gmail.com> writes:
On Feb 19, 3:04 am, Yukihiro Matsumoto <m...@ruby-lang.org> wrote:
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.
···
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
> 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.