Object#method and super. Is this a bug?

Baz#boo and Baz.new.method(:boo).call dosn't produce the same result.
Does this surprise anyone?

irb(main):001:0> class Foo
irb(main):002:1> def boo
irb(main):003:2> puts "Foo#boo"
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0>
irb(main):007:0* module Bar
irb(main):008:1> def boo
irb(main):009:2> puts "Bar#boo"
irb(main):010:2> super
irb(main):011:2> end
irb(main):012:1> end
=> nil
irb(main):013:0>
irb(main):014:0* class Baz < Foo
irb(main):015:1> include Bar
irb(main):016:1> end
=> Baz
irb(main):017:0>
irb(main):018:0* Baz.new.boo
Bar#boo
Foo#boo
=> nil
irb(main):019:0> Baz.new.method(:boo).call
Bar#boo
NoMethodError: super: no superclass method `boo'
        from (irb):10:in `boo'
        from (irb):19:in `call'
        from (irb):19
irb(main):020:0>

···

from :0

Hi!

Thus spake apefan@dodgeit.com on 04/21/2007 07:31 PM:

Baz#boo and Baz.new.method(:boo).call dosn't produce the same result.
Does this surprise anyone?

As far as I understand ruby's dynamic typing, that output is just
what you would expect:

When you create a new Baz and call it's boo(), you are actually
holding a class instance to do the dispatching: first the
module-included boo() is run and that looks for a boo() in the
_caller's_, i.e. this particular Bar's, superclass.

In the second case, you create a Baz, but then get hold of the
method itself by a process called "reflection" or "introspection".
Since everything in ruby is an object, that's what you get: a method
object representing the method itself.
So, in this case, you are holding the raw boo() method directly from
your Bar module. This method object does not know anything at all
about the instance that provided it, i.e. the new Baz you called
method() on.
It is just a sniplet of code, wrapped in an object for you.
Now, since Bar itself does not have a superclass, a call to super
_in_this_context_ yields the NoMethodError you encountered.

To cut a long story short, in the first case you call a method on an
instance of Baz and let ruby do all the dispatching for you. In the
second case you grab _through_ the Baz and pull the method directly
from your module and have it execute itself.

The prime thing to remember here is this: if you are using
reflection, always be clear of the context you call a method in, as
this might not always be the same for different approaches.

HTH,

  Phil

Interestingly, Baz.new.send(:boo) works as expected.

irb(main):018:0> Baz.new.send(:boo)
Bar#boo
Foo#boo
=> nil

I am curious to see what the final explanation is as well wrt method.

V/r
Anthony

···

On 4/21/07, apefan@dodgeit.com <apefan@dodgeit.com> wrote:

Baz#boo and Baz.new.method(:boo).call dosn't produce the same result.
Does this surprise anyone?

irb(main):001:0> class Foo
irb(main):002:1> def boo
irb(main):003:2> puts "Foo#boo"
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0>
irb(main):007:0* module Bar
irb(main):008:1> def boo
irb(main):009:2> puts "Bar#boo"
irb(main):010:2> super
irb(main):011:2> end
irb(main):012:1> end
=> nil
irb(main):013:0>
irb(main):014:0* class Baz < Foo
irb(main):015:1> include Bar
irb(main):016:1> end
=> Baz
irb(main):017:0>
irb(main):018:0* Baz.new.boo
Bar#boo
Foo#boo
=> nil
irb(main):019:0> Baz.new.method(:boo).call
Bar#boo
NoMethodError: super: no superclass method `boo'
        from (irb):10:in `boo'
        from (irb):19:in `call'
        from (irb):19
        from :0
irb(main):020:0>

--
Cell: 808 782-5046
Current Location: Melbourne, FL

This got broken in 1.8.3. This is indirectly caused by the patch in
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/3672,
namely when search_iclass actually got removed afterwards. The patch
below should fix things again. I hope it doesn't break anything else.

Peter

PS: Matz, should rb_class_name really call rb_class_real? I would have
expected rb_class_name to give the module name for ICLASSes, but it
doesn't so I needed to change method_inspect as well.

--- eval.c.old 2007-04-22 22:59:13.000000000 +0200
+++ eval.c 2007-04-22 23:03:22.000000000 +0200
@@ -8966,7 +8966,6 @@
      (FL_TEST(rklass, FL_SINGLETON) || TYPE(rklass) == T_ICLASS)) {
   rklass = RCLASS(rklass)->super;
     }
- if (TYPE(klass) == T_ICLASS) klass = RBASIC(klass)->klass;
     method = Data_Make_Struct(mklass, struct METHOD, bm_mark, free, data);
     data->klass = klass;
     data->recv = obj;
@@ -9410,7 +9409,7 @@
     VALUE method;
{
     struct METHOD *data;
- VALUE str;
+ VALUE str, klass;
     const char *s;
     char *sharp = "#";

@@ -9441,8 +9440,12 @@
     else {
   rb_str_buf_cat2(str, rb_class2name(data->rklass));
   if (data->rklass != data->klass) {
+ klass = data->klass;
+ if (TYPE(klass) == T_ICLASS) {
+ klass = RBASIC(klass)->klass;
+ }
       rb_str_buf_cat2(str, "(");
- rb_str_buf_cat2(str, rb_class2name(data->klass));
+ rb_str_buf_cat2(str, rb_class2name(klass));
       rb_str_buf_cat2(str, ")");
   }
     }

···

On 4/21/07, apefan@dodgeit.com <apefan@dodgeit.com> wrote:

Baz#boo and Baz.new.method(:boo).call dosn't produce the same result.
Does this surprise anyone?

Hi!

Thus spake apefan@dodgeit.com on 04/21/2007 07:31 PM:
> Baz#boo and Baz.new.method(:boo).call dosn't produce the same result.
> Does this surprise anyone?

As far as I understand ruby's dynamic typing, that output is just
what you would expect:

When you create a new Baz and call it's boo(), you are actually
holding a class instance to do the dispatching: first the
module-included boo() is run and that looks for a boo() in the
_caller's_, i.e. this particular Bar's, superclass.

In the second case, you create a Baz, but then get hold of the
method itself by a process called "reflection" or "introspection".
Since everything in ruby is an object, that's what you get: a method
object representing the method itself.
So, in this case, you are holding the raw boo() method directly from
your Bar module. This method object does not know anything at all
about the instance that provided it, i.e. the new Baz you called
method() on.

Sorry but this is not true, we get a method object bound to the
instance which can be called without any problem. Look at this code,
just to convince you

512/12 > cat simple.rb && ruby simple.rb
# vim: sts=2 sw=2 expandtab tw=0 nu:

class Baz
  def initialize
    @x = 42
  end
  def boo
    puts "Baz::boo @x=#{@x}"
  end # def boo
end

Baz.new.boo
Baz.new.method(:boo).call

Baz::boo @x=42

It is just a sniplet of code, wrapped in an object for you.

well you are saying it yourself here :wink:

Now, since Bar itself does not have a superclass, a call to super
_in_this_context_ yields the NoMethodError you encountered.

It does not seem to be right super should just work fine as we are in
the context of a Baz object and it can search the
self.class.ancestors list, which is
[Baz, Boo, Foo, Object, Kernel]
in both cases, just put
puts self.class.ancestors.inspect
into Boo::boo

The behavior seems wrong to me too , but hopefully a more learned
member of the list will enlighten us.

Cheers
Robert

To cut a long story short,

<snip> :wink:

Robert

···

On 4/22/07, Philipp Taprogge <Philipp.Taprogge@gmx.net> wrote:

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

Hi!

Thus spake Calamitas on 04/22/2007 11:39 PM:

This got broken in 1.8.3.

So it's a bug after all. I stand corrected :slight_smile:
Thanks for your clarification.

Regards,

  Phil

Thanks for the patch :slight_smile:
R.

···

On 4/22/07, Calamitas <calamitates@gmail.com> wrote:

On 4/21/07, apefan@dodgeit.com <apefan@dodgeit.com> wrote:
> Baz#boo and Baz.new.method(:boo).call dosn't produce the same result.
> Does this surprise anyone?

This got broken in 1.8.3. This is indirectly caused by the patch in
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/3672,
namely when search_iclass actually got removed afterwards. The patch
below should fix things again. I hope it doesn't break anything else.

Peter

PS: Matz, should rb_class_name really call rb_class_real? I would have
expected rb_class_name to give the module name for ICLASSes, but it
doesn't so I needed to change method_inspect as well.

--- eval.c.old 2007-04-22 22:59:13.000000000 +0200
+++ eval.c 2007-04-22 23:03:22.000000000 +0200
@@ -8966,7 +8966,6 @@
           (FL_TEST(rklass, FL_SINGLETON) || TYPE(rklass) == T_ICLASS)) {
        rklass = RCLASS(rklass)->super;
     }
- if (TYPE(klass) == T_ICLASS) klass = RBASIC(klass)->klass;
     method = Data_Make_Struct(mklass, struct METHOD, bm_mark, free, data);
     data->klass = klass;
     data->recv = obj;
@@ -9410,7 +9409,7 @@
     VALUE method;
{
     struct METHOD *data;
- VALUE str;
+ VALUE str, klass;
     const char *s;
     char *sharp = "#";

@@ -9441,8 +9440,12 @@
     else {
        rb_str_buf_cat2(str, rb_class2name(data->rklass));
        if (data->rklass != data->klass) {
+ klass = data->klass;
+ if (TYPE(klass) == T_ICLASS) {
+ klass = RBASIC(klass)->klass;
+ }
            rb_str_buf_cat2(str, "(");
- rb_str_buf_cat2(str, rb_class2name(data->klass));
+ rb_str_buf_cat2(str, rb_class2name(klass));
            rb_str_buf_cat2(str, ")");
        }
     }

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

Hi!

Thus spake Robert Dober on 04/22/2007 11:17 AM:

Sorry but this is not true, we get a method object bound to the
instance which can be called without any problem. Look at this code,
just to convince you

You are right in that the method object is bound to _an_ instance.
The question is: instance of what?

Your code differs from the OPs example in that your method is
defined inside the same class. If you grab this with method(), you
of course get a method object in the context of the instance that
defined it. Reflection would be no good if this was not the case.

If, however, the method is defined in another module and included,
the call to this method and all methods it calls itself are
dispatched at runtime.

In the OPs example, method() gets you the boo() method out of the
module and it shares the context of that module only, not the
instance including the module.

The behavior seems wrong to me too

Heh... this, of course, is a totally different question...
I might agree with you that a consistent behavior between native
method calls and reflected method calls would be better, but that
does not mean that the current behavior is a bug. As far as I
understand it, it's intended.

Regards,

  Phil

Hi!

Thus spake Robert Dober on 04/22/2007 11:17 AM:
> Sorry but this is not true, we get a method object bound to the
> instance which can be called without any problem. Look at this code,
> just to convince you

You are right in that the method object is bound to _an_ instance.
The question is: instance of what?

Your code differs from the OPs example in that your method is
defined inside the same class. If you grab this with method(), you
of course get a method object in the context of the instance that
defined it. Reflection would be no good if this was not the case.

Ok we agree on this, I did not read this in your mail, sorry.

If, however, the method is defined in another module and included,
the call to this method and all methods it calls itself are
dispatched at runtime.

Well but not more or less than if the method is defined in the class
directly, dispatching on runtime is another obvious need of the
dynamic nature of ruby. I honestly fail to see the difference.

In the OPs example, method() gets you the boo() method out of the
module

Maybe I am starting to read you here

the difference is that the included method is a method of the proxy
class/module of the mixin, it is not a method of the module though,
while the method you get when it is directly defined in the class is a
method of the class as one can see here
irb(main):055:0* module M
irb(main):056:1> def a; end
irb(main):057:1> end
=> nil
irb(main):058:0> class I
irb(main):059:1> include M
irb(main):060:1> end
=> I
irb(main):061:0> class S
irb(main):062:1> def a; end
irb(main):063:1> end
=> nil
irb(main):064:0> I.new.method(:a).inspect
=> "#<Method: I(M)#a>"

···

On 4/22/07, Philipp Taprogge <Philipp.Taprogge@gmx.net> wrote:
***************************
irb(main):065:0> S.new.method(:a).inspect
=> "#<Method: S#a>"
**************************

instance including the module.

> The behavior seems wrong to me too

Heh... this, of course, is a totally different question...
I might agree with you that a consistent behavior between native
method calls and reflected method calls would be better, but that
does not mean that the current behavior is a bug. As far as I
understand it, it's intended.

I do not claim to understand the full implications to hide super in
this case, but I would like to know more about it.
With my limited knowledge I feel the Proxy should behave completely
transparently.
And even if it is intended, it might still be worth changing.
Thx for the discussion so far.

Regards,

        Phil

Robert
--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

Hi again!

Thus spake Robert Dober on 04/22/2007 05:05 PM:

I do not claim to understand the full implications to hide super in
this case, but I would like to know more about it.

As would I...

With my limited knowledge I feel the Proxy should behave completely
transparently.

If you are coming from more strongly typed languages like Java,
that's certainly true. but even in Java, strange things can happen
when you start using reflection. While it is a powerful feature, it
also has it's pitfalls...

And even if it is intended, it might still be worth changing.

From a programmer's standpoint I totally agree, but there might be a
valid reason behind this. I don't understand enough of the inner
workings of ruby to judge on this one...
In any case, performance would propably suffer if each method object
would have to retain a reference to the whole hierarchy of it's
receivers.

Regards,

  Phil