delegation question, where I want prototype style delegation

S

Sam Roberts

I want to make an object that behaves like another object would, if that
object had had it's #each method redefined.

I can do this with extend(), but that permanently damages the object.

I can do it with delegate/method_missing, except I need to reimplement
every method of the delegatee, which sucks.

I could do this in prototype based languages, where you can effectively
create a new object that behaves like another object with a few differences,
but don't see a way in an OO language like ruby.

Below is an example of what I want to do, implemented with extend.

But it has a problem, it modifies the target object, but I may want
to create mutiple delegates to the
target, each with a different set of "views", that behave like the
target object would if it
had its #each method redefined.

Btw, what I'm actually doing is I have a calendar object, and I want
to create various views
of the calendar, ones including events but not todos, ones that appear
to only have
components that occur in a particular period, etc.

class Base
include Enumerable

def initialize(ary)
@ary = ary.to_a
end

def each
@ary.each{|a| yield a}
end

def show
inject("show: ") {|accum, o| accum + o.to_s + ","}
end
end

module Negate
def each(&block)
super do |a|
yield -a
end
end
end

module Add10
def each(&block)
super do |a|
yield a+10
end
end
end

o = Base.new(1..3)
o.extend Negate.dup
o.extend Add10
o.extend Negate.dup

o.each {|_| puts _ }

puts o.show
 
J

James Gray

I want to make an object that behaves like another object would, if
that
object had had it's #each method redefined.

What you really want in this case is normal inheritance, so the
overridden each() method replaces the original.

Then the only thick becomes getting the existing object into an
equivalent subclass form. We can use a little Ruby magic to track
subclasses and do the conversion for us:

class Base
def self.subclasses
@subclasses ||= [ ]
end

def self.inherited(subclass)
subclasses << subclass
end

def self.subclass(snake_case_name)
camel_case_name = snake_case_name.gsub(/(?:\A|_)(.)/)
{ $1.capitalize }
subclasses.find { |sc| sc.to_s == camel_case_name }
end

include Enumerable

def initialize(ary)
@ary = Array(ary)
end

def each(&block)
@ary.each(&block)
end

def show
"show: #{to_a.join(", ")}"
end

def method_missing(method, *args, &block)
if method.to_s =~ /\Aas_(\w+)\z/ and (sc = self.class.subclass($1))
sc.new(@ary)
else
super
end
end
end

class Negated < Base
def each
super { |e| yield -e }
end
end

class Plus10 < Base
def each
super { |e| yield e + 10 }
end
end

puts "Base:"
b = Base.new(1..3)
puts b.show

puts "Negated:"
puts b.as_negated.show

puts "Plus 10:"
puts b.as_plus_10.show

__END__

Hope that helps.

James Edward Gray II
 
A

ara.t.howard

But it has a problem, it modifies the target object, but I may want
to create mutiple delegates to the
target, each with a different set of "views", that behave like the
target object would if it
had its #each method redefined.

you can play with this:


cfp:~ > cat a.rb
class Base
instance_methods.each do |m|
unless m[%r/^__/]
old = m
new = "__#{ old }__"
new << '?' if new.sub!('?', '')
new << '!' if new.sub!('!', '')
alias_method new, old
undef_method old
end
end

def initialize object
@object = object
end

def method_missing m, *a, &b
@object.__send__ m, *a, &b
end

Delegates = {}

def / m
Delegates[m] ||=
__dup__.__instance_eval__ do
extend m
self
end
end
end

module Reverse
def each *a, &b
reverse.each *a, &b
end
end

a = Base.new [2,4]

p a.each{}

p (a/Reverse).each{}



cfp:~ > ruby a.rb
[2, 4]
[4, 2]




a @ http://codeforpeople.com/
 
S

Sam Roberts

What you really want in this case is normal inheritance, so the overridden
each() method replaces the original.

Educational code, but doesn't quite do the trick, because the views
aren't stackable.

In my example I negated, added 10, and negated again. With yours:

puts "Stacked operations:"
puts b.as_negated.as_plus_10.as_negated.show

=> in `method_missing': undefined method `as_plus_10' for
#<Negated:0x261ec @ary=[1, 2, 3]> (NoMethodError)

The end result of this would have to be an instance of

class Negated < Plus10 < Negated < Base
end

or something...

Also, my real class has much more complex internal state and
relationships, it isn't just
a wrapper for an Array, and creating a new instance isn't desireable.
I'm trying to create
calendar views, where the view of the calendar looks like the real
calendar (including
reflecting changes in the base calendar), but iterates over only dates
in a particular range,
or only journal entries.

Thanks,
Sam
 
S

Sam Roberts

Ara, thanks for the suggestion. It suffers from the same
non-stackability problem:

p ((a/Reverse)/Reverse).each{}
=> [4,2]

However, the combination of this and James' suggestion made me think
maybe I should be cloning my src object, and then modifying it.
clone() will get the singleton class, but won't do a deep copy, so it
will still share most of the internal state of the original class. I
think this will work for me:

Classes defined as in my original, but do the extending like:

o = Base.new(1..3)
n = o.clone.extend Negate.dup
an = n.clone.extend Add.dup
nan = an.clone.extend Negate.dup

puts o.show
puts n.show
puts an.show
puts nan.show


% ruby m.rb
show: 1,2,3,
show: -1,-2,-3,
show: 9,8,7,
show: -9,-8,-7,
 

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
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top