Strange behavior with SimpleDelegator and its Idioclass

T

Trans

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"
 
C

Christoph

Trans said:
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
 
C

Carlos

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.
 
C

Carlos

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.
 
T

Trans

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.
 
T

Trans

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!
 
C

Christoph

Trans said:
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
 
C

Carlos

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.
 
C

Christoph

--------------010209030801050101090704
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit
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.




--------------010209030801050101090704
Content-Type: text/plain;
name="delegate.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="delegate.rb"

# Delegation class that delegates even methods defined in super class,
# which can not be covered with normal method_missing hack.
#
# Delegator is the abstract delegation class. Need to redefine
# `__getobj__' method in the subclass. SimpleDelegator is the
# concrete subclass for simple delegation.
#
# Usage:
# foo = Object.new
# foo2 = SimpleDelegator.new(foo)
# foo.hash == foo2.hash # => false
#
# Foo = DelegateClass(Array)
#
# class ExtArray<DelegateClass(Array)
# ...
# end

class Delegator

def initialize(obj)
preserved = ::Kernel.public_instance_methods(false)
preserved -= ["to_s","to_a","inspect","==","=~","==="]
for t in self.class.ancestors
preserved |= t.public_instance_methods(false)
preserved |= t.private_instance_methods(false)
preserved |= t.protected_instance_methods(false)
break if t == Delegator
end
preserved << "singleton_method_added"
proxy = Module.new
for method in obj.methods
next if preserved.include? method
begin
proxy.module_eval <<-EOS
def self.#{method}(*args, &block)
begin
__getobj__.__send__:)#{method}, *args, &block)
rescue Exception
[email protected]_if{|s| /:in `__getobj__'$/ =~ s} #`
[email protected]_if{|s| /^\\(eval\\):/ =~ s}
Kernel::raise
end
end
EOS
rescue SyntaxError
raise NameError, "invalid identifier %s" % method, caller(4)
end
end
extend proxy
end
alias initialize_methods initialize

def __getobj__
raise NotImplementedError, "need to define `__getobj__'"
end

def marshal_dump
__getobj__
end
def marshal_load(obj)
initialize_methods(obj)
end
end

class SimpleDelegator<Delegator

def initialize(obj)
super
@_sd_obj = obj
end

def __getobj__
@_sd_obj
end

def __setobj__(obj)
@_sd_obj = obj
end

def clone
super
__setobj__(__getobj__.clone)
end
def dup(obj)
super
__setobj__(__getobj__.dup)
end
end

# backward compatibility ^_^;;;
Delegater = Delegator
SimpleDelegater = SimpleDelegator

#
def DelegateClass(superclass)
klass = Class.new
methods = superclass.public_instance_methods(true)
methods -= ::Kernel.public_instance_methods(false)
methods |= ["to_s","to_a","inspect","==","=~","==="]
klass.module_eval {
def initialize(obj)
@_dc_obj = obj
end
def method_missing(m, *args)
unless @_dc_obj.respond_to?(m)
super(m, *args)
end
@_dc_obj.__send__(m, *args)
end
def __getobj__
@_dc_obj
end
def __setobj__(obj)
@_dc_obj = obj
end
def clone
super
__setobj__(__getobj__.clone)
end
def dup
super
__setobj__(__getobj__.dup)
end
}
for method in methods
begin
klass.module_eval <<-EOS
def #{method}(*args, &block)
begin
@_dc_obj.__send__:)#{method}, *args, &block)
rescue
$@[0,2] = nil
raise
end
end
EOS
rescue SyntaxError
raise NameError, "invalid identifier %s" % method, caller(3)
end
end
return klass
end

if __FILE__ == $0
class ExtArray<DelegateClass(Array)
def initialize()
super([])
end
end

ary = ExtArray.new
p ary.class
ary.push 25
p ary

foo = Object.new
def foo.test
25
end
def foo.error
raise 'this is OK'
end
foo2 = SimpleDelegator.new(foo)
p foo.test == foo2.test # => true
foo2.error # raise error!
end

--------------010209030801050101090704--
 
T

Trans

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.
 
T

Trans

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.
 
C

Christoph

Trans said:
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
 
T

Trans

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.
 
T

Trans

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.
 
T

Trans

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

T.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,171
Messages
2,570,935
Members
47,472
Latest member
KarissaBor

Latest Threads

Top