Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > Dynamically extending modules once they have been included

Reply
Thread Tools

Dynamically extending modules once they have been included

 
 
James Coglan
Guest
Posts: n/a
 
      03-17-2009
[Note: parts of this message were removed to make it a legal post.]

> The set of expression classes is finite. In my extension to Runt, I
> need to add some behavior to the TExpr module which I do with another
> mixin. I didn't want to have to worry about which classes mixin the
> TExpr, I just wanted to modify(aka extend) the TExpr module like you
> would any ordinary class and have it be reflected in all classes who
> included this module. The alternative(which is what I'm ending up
> doing) is to loop through all the classes of Runt and check the
> ancestors for TExpr and if so, include my extension module directly
> into the class. This approach seems yucky to me and not in the spirit
> of ruby and really OO design.



Yes, this is very yucky and you shouldn't have to do it. Unfortunately I
can't think of any elegant way to do this without inspecting the class tree
using your method or ObjectSpace (you can't get the descendants of a module
directly, so I use ObjectSpace which is kinda expensive).


> You can clearly argue with me whether you think extending a module
> with another is appropriate or not in the above case. However, the
> issue remains that this behavior is not consistent with the rest of
> ruby. I point again to the fact that you can change behavior of a
> module directly and it will be reflected by all included classes -
> however NOT if you change behavior via including another module.



You ought to be able to just say you want to change all the objects of
such-and-such a type, whether that type is a module or a class. You could of
course put your new methods straight into TExpr, which would work but could
cause debugging and other monkey-patching issues. It's up to you whether you
consider this a problem.


>
> Also, I don't necessarily(keyword here) agree with the philosophical
> point of needing 'a lot' of use cases to implement a feature.



I am very much speculating here, but I'd be surprised if this weren't a case
of, not implementing something as such, but rather one of removing special
cases. One way to implement Ruby's object system is that you construct the
whole thing out of modules (a module is an object that has one method table
and zero or more 'parent' modules), then things like parent-child
inheritance and singleton classes can be implemented on top of that. This
ends up being quite elegant and I suspect this is a performance issue.

Far as I know, Ruby finds methods by getting a list of an object's ancestors
and extracting all implementations of the method from those modules. Walking
up a series of parent classes takes linear time so it's not expensive for
classes to see stuff added to their parents, but walking and flattening a
multiple inheritance tree could be exponential time so it makes sense to
cache ancestor trees when things like #include are called, effectively
blocking a class from seeing changes to its mixins' ancestry.

If anyone knows how Module/Class are really implemented in MRI I'd love to
know more about it (also: I really need to learn C).

--
James Coglan
http://github.com/jcoglan

 
Reply With Quote
 
 
 
 
Synth
Guest
Posts: n/a
 
      03-17-2009

> Far as I know, Ruby finds methods by getting a list of an object's ancestors
> and extracting all implementations of the method from those modules. Walking
> up a series of parent classes takes linear time so it's not expensive for
> classes to see stuff added to their parents, but walking and flattening a
> multiple inheritance tree could be exponential time so it makes sense to
> cache ancestor trees when things like #include are called, effectively
> blocking a class from seeing changes to its mixins' ancestry.


Indeed, if every module had pointers to other included modules, I
definitely see how you'd end up with a very expensive object graph to
traverse to find method definitions. Using the wonders of ruby, I
found a (rudimentary)way you can easily implement this though(see
below). Its just a more generalized version of what I have above. If
the only consideration is performance, I don't think its that much of
a hit(depending on your ObjectSpace), plus when you do any
metaprogramming I don't think performance is a major concern. In this
case, any performance hit will occur only once at "include-time" and I
can't really envision injecting modules of code at any great rate -
although maybe this is just my short-sightedness Anywhere, here's
the workaround:

class Module;
def included(base)
return unless(base.class == Module)
ObjectSpace.each_object(Class){|o|
next unless o.ancestors.include?(base)
o.send(:include, self)
}
end
end

A simple benchmark on my system with irb and the example taken from
Eigenclass's page:
>> require 'benchmark'
>> class Module; def included(base); return unless(base.class == Module);ObjectSpace.each_object(Class){|o| next unless o.ancestors.include?(base);o.send(:include, self)};end;end
>> Benchmark.realtime{ ?> module A; end
>> class C; include A end
>> module B; def foo; "B#foo" end end
>> module A; include B end
>> class D; include A end
>> }

=> 0.00185704231262207

not too shabby, no?


 
Reply With Quote
 
 
 
 
James Coglan
Guest
Posts: n/a
 
      03-17-2009
[Note: parts of this message were removed to make it a legal post.]

2009/3/17 Synth <(E-Mail Removed)>

>
> > Far as I know, Ruby finds methods by getting a list of an object's

> ancestors
> > and extracting all implementations of the method from those modules.

> Walking
> > up a series of parent classes takes linear time so it's not expensive for
> > classes to see stuff added to their parents, but walking and flattening a
> > multiple inheritance tree could be exponential time so it makes sense to
> > cache ancestor trees when things like #include are called, effectively
> > blocking a class from seeing changes to its mixins' ancestry.

>
> Indeed, if every module had pointers to other included modules, I
> definitely see how you'd end up with a very expensive object graph to
> traverse to find method definitions. Using the wonders of ruby, I
> found a (rudimentary)way you can easily implement this though(see
> below). Its just a more generalized version of what I have above. If
> the only consideration is performance, I don't think its that much of
> a hit(depending on your ObjectSpace), plus when you do any
> metaprogramming I don't think performance is a major concern. In this
> case, any performance hit will occur only once at "include-time" and I
> can't really envision injecting modules of code at any great rate -
> although maybe this is just my short-sightedness Anywhere, here's
> the workaround:
>
> class Module;
> def included(base)
> return unless(base.class == Module)
> ObjectSpace.each_object(Class){|o|
> next unless o.ancestors.include?(base)
> o.send(:include, self)
> }
> end
> end




Cool. You'll probably want something to handle extended() as well, for where
modules have been mixed into singleton classes. Calling
object.extend(module) is effectively the same as:

class << object
include module
end

except that a different callback is called.

For the curious, here's my implementation of the Ruby object model in
JavaScript:
http://github.com/jcoglan/js.class/t...r/source/core/

It's reasonably documented; every module maintains both a list of its mixins
and a list of its descendants as I need to propagate new methods down the
inheritance chain. Each class has a module where it stores all its methods.
Whenever an #include takes place, I run the tree and cache all the resulting
methods on the class itself; I could probably do this for ancestry as well
without running into Ruby's problems, since I can easily get modules to
notify their descendants when they are modified so I can update all the
cached method/ancestor tables.

 
Reply With Quote
 
Calamitas
Guest
Posts: n/a
 
      03-18-2009
On Tue, Mar 17, 2009 at 1:12 AM, <(E-Mail Removed)> wrote:
> The only alternative I've find is to loop through the ObjectSpace
> (filtering on classes) and find out which class's ancestors include
> the module I want to extend and then include my extension module
> straight into the class.


Below is code taken from my FOSDEM presentation. It tries to tackle
the problem you are describing. It is not well-tested or anything, but
it did pass the examples I used in the presentation. Just execute this
code before any of your module definitions and see if it helps.

Peter


class Module

def included_in
@included_in ||= {}
end

def included(m)
unless included_in[m]
included_in[m] = true
m.update_inclusions
end
end

def extended(m)
included(class << m ; self ; end)
end

def update_inclusions
included_in.each do |m, _|
m.send(:include, self)
m.update_inclusions
end
end

end

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Method lookup for modules included in modules Mark Wilden Ruby 5 05-15-2008 08:24 PM
Adding code/Extending methods after they have been written? Marc Heiler Ruby 1 01-26-2008 09:26 PM
Self-awareness of imported modules? Do they know where they live? Martin M. Python 4 12-15-2005 01:24 PM
they turn, they power, they make nice pics Keith and Jenn Z. Digital Photography 0 09-21-2003 04:16 AM



Advertisments