[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Strange behavior with SimpleDelegator and its Idioclass

Trans

4/25/2005 12:02:00 AM

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"

15 Answers

Christoph R.

4/25/2005 1:37:00 AM

0

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



Carlos

4/25/2005 2:06:00 AM

0

[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

4/25/2005 2:11:00 AM

0

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


Trans

4/25/2005 2:42:00 AM

0

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.

Trans

4/25/2005 2:50:00 AM

0

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!

Christoph R.

4/25/2005 3:23:00 AM

0

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




Carlos

4/25/2005 3:29:00 AM

0

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


Christoph R.

4/25/2005 3:38:00 AM

0

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.



# 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
$@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
$@.delete_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

Trans

4/25/2005 3:46:00 AM

0

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.

Trans

4/25/2005 3:52:00 AM

0

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.