Strange behavior with SimpleDelegator and its Idioclass

Have a look at this. Run it as is, then unremark the comment section.
Something strange is a foot here.

require 'delegate'

M = Module.new {
def bracket
   p "come on"
end
}

class SD < SimpleDelegator

def initialize( obj )
   super( obj )
   s = (class << self ; self ; end)
   s.class_eval { include M }
end

# try unremarking this
#def bracket
# p "hi"
#end

end

class C

def bracket
   p "hello"
end

end

c = C.new
sd = SD.new(c)
sd.bracket

# my results
# w/ comment : "hello"
# w/o comment : "come on"

Trans schrieb:

Have a look at this. Run it as is, then unremark the comment section.
Something strange is a foot here.

Brownings the source of the Delegator.rb is becomes clear that
it is not mend to be deal with this kind of abuse - also your
example is obscured by a superfluous block usage.

require 'delegate'

M = Module.new {
def bracket
  p "come on"
end

module M
    def bracket
       p "come on"
    end
end

class SD < SimpleDelegator

def initialize( obj )
  super( obj )
  s = (class << self ; self ; end)
  s.class_eval { include M }

  replace the last two lines with
  extend M

/Christoph

[Trans <transfire@gmail.com>, 2005-04-25 02.04 CEST]

Have a look at this. Run it as is, then unremark the comment section.
Something strange is a foot here.

[ snip example ]

# my results
# w/ comment : "hello"
# w/o comment : "come on"

Two things here:

First, methods in the Delegator class (or subclass) are not forwarded:

  require 'delegate.rb'
  class SD < SimpleDelegator
          def b
                  puts "sb.b"
          end
  end

  class X
          def b
                  puts "x.b"
          end
  end

  x=X.new
  sd = SD.new(x)
  sd.b # => "sb.b"

Second, when you include a module, it overwrites (overrides? obscures?) any
method of the same name that you had:

  module M
          def b
                  puts "m.b"
          end
  end
   
  class WW
          def initialize
                  (class<<self;self;end).class_eval{ include M }
          end
          def b
                  puts "ww.b"
          end
  end
   
  WW.new.b # => m.b

Combination of the two: your case.

Good luck.

[Carlos <angus@quovadis.com.ar>, 2005-04-25 04.06 CEST]

[Trans <transfire@gmail.com>, 2005-04-25 02.04 CEST]
> Have a look at this. Run it as is, then unremark the comment section.
> Something strange is a foot here.
[ snip example ]
> # my results
> # w/ comment : "hello"
> # w/o comment : "come on"

First, methods in the Delegator class (or subclass) are not forwarded:

I might add that (in your code, which I stupidly snipped), the code to
forward #bracket to the object was already done when the module was
included.

Hi Cristoph,

also your example is obscured by a superfluous block usage.

partly that's because it is a simplification of a more generalized
program I'm working on. But yes I can simplify some thanks.

T.

Perhaps I should point out that the strange thing about this is that
when the #bracket method in SD is NOT defined, then the module included
in the idioclass (singleton) is not called as one would expect it to
be. But when you add #bracket into SD, it "suddenly" works as it
should, and subsequently by passes the #bracket method just defined!

Trans schrieb:

Perhaps I should point out that the strange thing about this is that
when the #bracket method in SD is NOT defined, then the module included
in the idioclass (singleton) is not called as one would expect it to
be. But when you add #bracket into SD, it "suddenly" works as it
should, and subsequently by passes the #bracket method just defined!

You really have to look at the source code in delegator.rb.
Instantiation creates a singleton method for all forwarded
methods - However some methods are not forwarded
(preserved) for example when they already defined in the
Delegator sub class. Now combine this with the following
behavior

···

---
module A
  def foo
    "subordinate"
  end
end

class << a = Object.new
  def foo
    "dominate"
  end
  include A
end

p a.foo # dominate
--
and you have your explanation

/Christoph

[Trans <transfire@gmail.com>, 2005-04-25 04.54 CEST]

Perhaps I should point out that the strange thing about this is that
when the #bracket method in SD is NOT defined, then the module included
in the idioclass (singleton) is not called as one would expect it to
be. But when you add #bracket into SD, it "suddenly" works as it
should, and subsequently by passes the #bracket method just defined!

Ok... another try to explanation. I think I know what part of information
you were missing.

1.
class C
    include M
    def x
    end
end

If M defines x, C#x will not be overriden, ok? You oversaw this, I think.

2.
class X; def b; puts "x.b"; end

class SD < SimpleDelegator
    def initialize
        super # here self.b is defined to forward to X#b (1)
        (class<<self;self;end).class_eval{include M}
                   # doesn't matter if M defines #b,
                   # self.b is already defined and won't be
                   # overriden (2). self.b forwards to X#b.
    end

    # def b
    # puts "sd.b"
    # end

# but if you uncomment the lines above, (1) doesn't happen (see my previous
# message), so (2) do add #b to the singleton class, and since methods are
# first searched in the singleton class, this #b will be called instead of
# SD#b.
end

Good luck.

Trans schrieb:

Perhaps I should point out that the strange thing about this is that
when the #bracket method in SD is NOT defined, then the module included
in the idioclass (singleton) is not called as one would expect it to
be. But when you add #bracket into SD, it "suddenly" works as it
should, and subsequently by passes the #bracket method just defined!

For the record I included a small modification of delegate.rb which matches
your expectation.

delegate.rb (3.38 KB)

Carlos and Christoph,

Thanks. I see what you're both syaing now. That's too bad in that it
doesn't help my use case. In a way I consider this a bug. Although is
understandable why it does this. I see what I can do to get around it,
better yet I'll look at delegate.rb to see if there's a way to take
this into account. Perhpas make it check for singleton methods too.

T.

Cranky! Thanks Cristoph! I was just about to take a look at that
myself. You're the bomb! Do you think this is worthy of inclusion in
Ruby?

T.

Correction:
          def self.#{method}(*args, &block)

shoud be:
          def #{method}(*args, &block)

in:
  proxy = Module.new
      for method in obj.methods
        next if preserved.include? method
        begin
          proxy.module_eval <<-EOS
            def #{method}(*args, &block)
              begin

T.

Trans schrieb:

Cranky! Thanks Cristoph! I was just about to take a look at that
myself. You're the bomb! Do you think this is worthy of inclusion in
Ruby?

T.

I suppose it wouldn't mess up any thing - but why not create a patch
and ask Matz about it?

/Christoph

Trans schrieb:

Correction:
         def self.#{method}(*args, &block)

shoud be:
         def #{method}(*args, &block)

Yes sorry ...

/Christoph

Okay, now I'm trying to better understand how delegate.rb works b/c I
would like to reuse the principle behind it elsewhere. But I don't
understand one thing. How is self being redirected?

  class SD < SimpleDelegator
    def bracket
      p self
    end
  end

  class C
  end

  c = C.new
  sd = SD.new(c)
  sd.bracket

produces

  #<C:0x40329350>

How does self become c in the context of sd? I don't see how
delegate.rb achieves this.

Thanks,
T.

Well, looks like it's a trick of #inspect being redirected (also #to_s,
#to_a, #==, #=~, and #===).

T.