From: Yusuke ENDOH Date: 2010-03-20T00:45:31+09:00 Subject: [ruby-core:28787] Re: [Feature #2065] An ancestors iterator Hi, 2010/3/20 Simon Chiang : > So to clarify... ?How much any piece of code of a bottleneck depends on how frequently the code gets run, and that is application-specific. ?In my own application, it IS enough of a bottleneck for me to make this request because I run the code very frequently. ?In other applications, it may not be a bottleneck. > > For DSLs that want to have method-like inheritance, the lack of an ancestors iterator is a potential and unnecessary bottleneck. ?An each_ancestor iterator would, as per the benchmarks, significantly speed up a useful programming pattern that I think would be used more frequently if it were inherently faster. I hope that clarifies the purpose of this request. I doubt if there is few real application whose bottleneck is Module#ancestors. > Using superclass would work in the example I gave you but not in general because as I understand it superclass does not visit modules. ?Using ancestors allows for method-like 'inheritance' because you can visit both modules and superclasses. Using superclass limits you to the class hierarchy. Indeed. I'd like to make up for my stupid suggestion by giving a patch :-) I can commit this if matz approves. diff --git a/class.c b/class.c index fed2edf..51bc162 100644 --- a/class.c +++ b/class.c @@ -763,6 +763,42 @@ rb_mod_ancestors(VALUE mod) return ary; } +/* + * call-seq: + * mod.each_ancestor -> nil + * + * Yields each module included in mod (including mod + * itself). + * + * module Mod + * include Math + * include Comparable + * end + * + * Mod.each_ancestor {|c| p c } #=> Mod, Comparable, Math + * Math.each_ancestor {|c| p c } #=> Math + */ + +VALUE +rb_mod_each_ancestor(VALUE mod) +{ + VALUE p; + + RETURN_ENUMERATOR(mod, 0, 0); + + for (p = mod; p; p = RCLASS_SUPER(p)) { + if (FL_TEST(p, FL_SINGLETON)) + continue; + if (BUILTIN_TYPE(p) == T_ICLASS) { + rb_yield(RBASIC(p)->klass); + } + else { + rb_yield(p); + } + } + return Qnil; +} + #define VISI(x) ((x)&NOEX_MASK) #define VISI_CHECK(x,f) (VISI(x) == (f)) diff --git a/object.c b/object.c index 0421824..6c8766e 100644 --- a/object.c +++ b/object.c @@ -2449,6 +2449,8 @@ rb_f_array(VALUE obj, VALUE arg) return rb_Array(arg); } +extern VALUE rb_mod_each_ancestor(VALUE mod); + /* * Document-class: Class * @@ -2657,6 +2659,7 @@ Init_Object(void) rb_define_method(rb_cModule, "include?", rb_mod_include_p, 1); /* in class.c */ rb_define_method(rb_cModule, "name", rb_mod_name, 0); /* in variable.c */ rb_define_method(rb_cModule, "ancestors", rb_mod_ancestors, 0); /* in class.c */ + rb_define_method(rb_cModule, "each_ancestor", rb_mod_each_ancestor, 0); /* in class.c */ rb_define_private_method(rb_cModule, "attr", rb_mod_attr, -1); rb_define_private_method(rb_cModule, "attr_reader", rb_mod_attr_reader, -1); diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index f905431..4330d69 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -216,6 +216,15 @@ class TestModule < Test::Unit::TestCase remove_rake_mixins(remove_json_mixins(remove_pp_mixins(String.ancestors)))) end + def test_each_ancestor + a = [] + User.each_ancestor {|m| a << m } + assert_equal([User, Mixin], a) + a = [] + Mixin.each_ancestor {|m| a << m } + assert_equal([Mixin], a) + end + CLASS_EVAL = 2 @@class_eval = 'b' -- Yusuke ENDOH