delegation question, where I want prototype style delegation

Discussion in 'Ruby' started by Sam Roberts, May 6, 2008.

  1. Sam Roberts

    Sam Roberts Guest

    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
    Sam Roberts, May 6, 2008
    #1
    1. Advertising

  2. Sam Roberts

    James Gray Guest

    On May 6, 2008, at 1:00 AM, Sam Roberts wrote:

    > 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
    James Gray, May 6, 2008
    #2
    1. Advertising

  3. Sam Roberts

    ara.t.howard Guest

    On May 6, 2008, at 12:00 AM, Sam Roberts wrote:
    > 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/
    --
    we can deny everything, except that we have the possibility of being
    better. simply reflect on that.
    h.h. the 14th dalai lama
    ara.t.howard, May 6, 2008
    #3
  4. Sam Roberts

    Sam Roberts Guest

    On Tue, May 6, 2008 at 5:58 AM, James Gray <> wrote:
    > On May 6, 2008, at 1:00 AM, Sam Roberts wrote:
    >
    >
    > > 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.


    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
    Sam Roberts, May 7, 2008
    #4
  5. Sam Roberts

    Sam Roberts Guest

    On Tue, May 6, 2008 at 7:25 AM, ara.t.howard <> wrote:

    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,
    Sam Roberts, May 7, 2008
    #5
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. June Lee
    Replies:
    2
    Views:
    786
    Jim Cobban
    Apr 13, 2008
  2. Replies:
    9
    Views:
    173
    Thomas 'PointedEars' Lahn
    May 26, 2006
  3. Replies:
    3
    Views:
    257
  4. cbare
    Replies:
    8
    Views:
    169
    John G Harris
    Oct 31, 2007
  5. cbare
    Replies:
    1
    Views:
    88
    cbare
    Nov 1, 2007
Loading...

Share This Page