idioms for dup/clone

Discussion in 'Ruby' started by Paul Brannan, Jun 14, 2004.

  1. Paul Brannan

    Paul Brannan Guest

    Suppose I have an class that needs to implement its own dup/clone
    methods. What is the correct way to write these methods?

    One way is to create protected accessors:

    class Foo
    def initialize(foo, bar)
    @foo = foo
    @bar = bar
    end

    def dup
    copy = super
    copy.foo = @foo.dup
    copy.bar = @bar.dup
    end

    protected
    attr_accessor :foo
    attr_accessor :bar
    end

    Another way is to use instance_eval:

    class Foo
    def dup
    copy = super
    foo = @foo.dup
    bar = @bar.dup
    copy.instance_eval do
    @foo = foo
    @bar = bar
    end
    end
    end

    Questions:

    1. How else might dup/clone be implemented? (the deep-copy trick using
    marshaling is one way, but the goal here is to write a one-level-deep
    dup/clone).

    2. What is the right way to set the new instance variables in the copy?

    3. Should dup be implemented in terms of clone or should clone be
    implented in terms of dup (or should they both be implemented
    independently or in terms of another function)?

    4. When making the copy, is super the right way to make the copy, or
    should allocate be used? (allocate doesn't work on 1.6.x, which I
    still use heavily, though if it's the right solution, then it's the
    right solution).

    5. Certain types cannot be dup'd (e.g. NilClass in all versions of Ruby
    or Fixnum in 1.8 and later), so the above code will break if I write:

    Foo.new(10, 42).dup

    Is it possible (or is it even wise) to write a dup or clone function
    that works with both value types and container types?

    Paul
     
    Paul Brannan, Jun 14, 2004
    #1
    1. Advertising

  2. Paul Brannan

    ts Guest

    >>>>> "P" == Paul Brannan <> writes:

    P> 3. Should dup be implemented in terms of clone or should clone be
    P> implented in terms of dup (or should they both be implemented
    P> independently or in terms of another function)?

    For 1.6 Kernel#dup is implemented via Kernel#clone

    1.8 use #initialize_copy



    Guy Decoux
     
    ts, Jun 14, 2004
    #2
    1. Advertising

  3. Just some thoughs and code snippets - no general solutions here...

    "Paul Brannan" <> schrieb im Newsbeitrag
    news:...
    > Suppose I have an class that needs to implement its own dup/clone
    > methods. What is the correct way to write these methods?
    >
    > One way is to create protected accessors:
    >
    > class Foo
    > def initialize(foo, bar)
    > @foo = foo
    > @bar = bar
    > end
    >
    > def dup
    > copy = super
    > copy.foo = @foo.dup
    > copy.bar = @bar.dup
    > end


    You need to return 'copy' here.

    > protected
    > attr_accessor :foo
    > attr_accessor :bar
    > end
    >
    > Another way is to use instance_eval:
    >
    > class Foo
    > def dup
    > copy = super
    > foo = @foo.dup
    > bar = @bar.dup
    > copy.instance_eval do
    > @foo = foo
    > @bar = bar
    > end


    You need to return 'copy' here.

    > end
    > end
    >
    > Questions:
    >
    > 1. How else might dup/clone be implemented? (the deep-copy trick using
    > marshaling is one way, but the goal here is to write a one-level-deep
    > dup/clone).
    >
    > 2. What is the right way to set the new instance variables in the copy?


    I guess it really depends...

    > 3. Should dup be implemented in terms of clone or should clone be
    > implented in terms of dup (or should they both be implemented
    > independently or in terms of another function)?


    IMHO neither since #dup and #clone do have different semantics with
    respect to freeze. Well, you could do something like this:

    class Foo
    attr_accessor :foo, :bar

    def dup; do_copy( super, :dup ); end
    def clone; do_copy( super, :clone ); end

    protected
    def do_copy(copy, sym)
    copy.instance_eval do
    @foo = @foo.send sym
    @bar = @bar.send sym
    end

    copy
    end
    end

    but this does not work for #clone if the instance is frozen.

    This might be better:

    class Foo
    attr_accessor :foo, :bar

    def clone; copy :clone; end
    def dup; copy :dup; end

    protected
    def copy(sym)
    c = self.class.new

    copy_init c

    c.freeze? if frozen
    c.taint if tainted?

    c
    end

    def copy_init(c)
    c.instance_eval do
    @foo = @foo.sent sym
    @bar = @bar.sent sym
    end
    end
    end

    class Bar < Foo
    attr_accessor :name

    protected
    def copy_init(c)
    super
    c.instance_eval do
    @name = @name.send sym
    end
    end
    end


    > 4. When making the copy, is super the right way to make the copy, or
    > should allocate be used? (allocate doesn't work on 1.6.x, which I
    > still use heavily, though if it's the right solution, then it's the
    > right solution).


    I'm inclined to follow the Java clone() pattern and use super. Of course
    you can also just do self.class.new.

    > 5. Certain types cannot be dup'd (e.g. NilClass in all versions of Ruby
    > or Fixnum in 1.8 and later), so the above code will break if I write:
    >
    > Foo.new(10, 42).dup
    >
    > Is it possible (or is it even wise) to write a dup or clone function
    > that works with both value types and container types?


    It depends on whether you expect these types as members and what you want
    to be done with them. It's difficult to anwer this generally, that's why
    the automatic methods just do a shallow copy.

    Generally speaking IMHO NilClass#dup should return self, same for Fixnums
    and others. But OTOH I can see why Matz did it this way: so you get to
    know that some instance is not cloneable.

    Kind regards

    robert
     
    Robert Klemme, Jun 14, 2004
    #3
  4. "ts" <> schrieb im Newsbeitrag
    news:...
    > >>>>> "P" == Paul Brannan <> writes:

    >
    > P> 3. Should dup be implemented in terms of clone or should clone be
    > P> implented in terms of dup (or should they both be implemented
    > P> independently or in terms of another function)?
    >
    > For 1.6 Kernel#dup is implemented via Kernel#clone
    >
    > 1.8 use #initialize_copy


    It does even seem to work with frozen instances. Good to know. Thanks for
    that hint!

    Regards

    robert
     
    Robert Klemme, Jun 14, 2004
    #4
  5. Paul Brannan

    Christoph Guest

    ts wrote:

    ....
    > P> 3. Should dup be implemented in terms of clone or should clone be
    > P> implented in terms of dup (or should they both be implemented
    > P> independently or in terms of another function)?
    >
    > For 1.6 Kernel#dup is implemented via Kernel#clone
    >
    > 1.8 use #initialize_copy


    When redefining #initialize_copy it might be vice or
    a virtue to call or not to call or when to call "super" -
    for example

    ---
    class AccessHash < Hash
    def initialize
    @accessed = 0
    super
    end
    def [](key)
    @accessed += 1
    super
    end

    def out
    p self, @accessed
    end

    protected
    attr_reader :accessed

    # first try
    def initialize_copy(orig)
    @accessed = 0 # reset @accessed
    end
    end

    h = AccessHash.new
    h[1] = 1
    h[2]
    h[1]


    h.clone.out # {} - uups, empty AccessHash
    # 0

    class AccessHash < Hash
    def initialize_copy(orig)
    super
    end
    end

    h.clone.out # {1=>1}
    # 2 - uups, @accessed is wrong

    class AccessHash < Hash
    def initialize_copy(orig)
    @accessed = 0
    super
    end
    end

    h.clone.out # {1=>1}
    # 0 - works, interesting!!!

    # okay just checking

    class AccessHash < Hash
    def initialize_copy(orig)
    super
    @accessed = 0
    end
    end

    h.clone.out # {1=>1}
    # 0
    ---

    /Christoph
     
    Christoph, Jun 14, 2004
    #5
  6. Paul Brannan

    Paul Brannan Guest

    On Tue, Jun 15, 2004 at 12:08:37AM +0900, Robert Klemme wrote:
    > You need to return 'copy' here.


    Good catch, thanks.

    > > 3. Should dup be implemented in terms of clone or should clone be
    > > implented in terms of dup (or should they both be implemented
    > > independently or in terms of another function)?

    >
    > IMHO neither since #dup and #clone do have different semantics with
    > respect to freeze. Well, you could do something like this:
    >
    > class Foo
    > attr_accessor :foo, :bar
    >
    > def dup; do_copy( super, :dup ); end
    > def clone; do_copy( super, :clone ); end
    >
    > protected
    > def do_copy(copy, sym)
    > copy.instance_eval do
    > @foo = @foo.send sym
    > @bar = @bar.send sym
    > end
    >
    > copy
    > end
    > end


    I like this, since it works on both 1.6 and 1.8.

    > but this does not work for #clone if the instance is frozen.


    Right, because the copy has already been frozen when we start to modify
    it. I wonder if there's a good way around that (without using
    initialize_copy, since that doesn't work on 1.6). Maybe I should
    re-implement clone/dup in ruby (as you've done below) for Object so that
    it has the same behavior as 1.8.

    > This might be better:
    >


    It's much better, though it seems a bit heavyweight.

    > class Foo
    > attr_accessor :foo, :bar
    >
    > def clone; copy :clone; end
    > def dup; copy :dup; end
    >
    > protected
    > def copy(sym)
    > c = self.class.new
    >
    > copy_init c
    >
    > c.freeze? if frozen
    > c.taint if tainted?


    I think this should read:

    if sym == :clone then
    c.freeze if frozen?
    c.taint if tainted?
    end

    >
    > c
    > end
    >
    > def copy_init(c)
    > c.instance_eval do
    > @foo = @foo.sent sym
    > @bar = @bar.sent sym
    > end
    > end
    > end
    >
    > class Bar < Foo
    > attr_accessor :name
    >
    > protected
    > def copy_init(c)
    > super
    > c.instance_eval do
    > @name = @name.send sym
    > end
    > end
    > end
    >
    >
    > > 4. When making the copy, is super the right way to make the copy, or
    > > should allocate be used? (allocate doesn't work on 1.6.x, which I
    > > still use heavily, though if it's the right solution, then it's the
    > > right solution).

    >
    > I'm inclined to follow the Java clone() pattern and use super. Of course
    > you can also just do self.class.new.


    The problem with self.class.new is that the initialize method may have
    some semantics that are undesirable when copying.

    > > 5. Certain types cannot be dup'd (e.g. NilClass in all versions of Ruby
    > > or Fixnum in 1.8 and later), so the above code will break if I write:
    > >
    > > Foo.new(10, 42).dup
    > >
    > > Is it possible (or is it even wise) to write a dup or clone function
    > > that works with both value types and container types?

    >
    > It depends on whether you expect these types as members and what you want
    > to be done with them. It's difficult to anwer this generally, that's why
    > the automatic methods just do a shallow copy.
    >
    > Generally speaking IMHO NilClass#dup should return self, same for Fixnums
    > and others. But OTOH I can see why Matz did it this way: so you get to
    > know that some instance is not cloneable.


    That's exactly what I was thinking, though I was hoping for a better
    answer.

    Paul
     
    Paul Brannan, Jun 15, 2004
    #6
  7. "Paul Brannan" <> schrieb im Newsbeitrag
    news:...
    > On Tue, Jun 15, 2004 at 12:08:37AM +0900, Robert Klemme wrote:
    > > You need to return 'copy' here.

    >
    > Good catch, thanks.
    >
    > > > 3. Should dup be implemented in terms of clone or should clone be
    > > > implented in terms of dup (or should they both be implemented
    > > > independently or in terms of another function)?

    > >
    > > IMHO neither since #dup and #clone do have different semantics with
    > > respect to freeze. Well, you could do something like this:
    > >
    > > class Foo
    > > attr_accessor :foo, :bar
    > >
    > > def dup; do_copy( super, :dup ); end
    > > def clone; do_copy( super, :clone ); end
    > >
    > > protected
    > > def do_copy(copy, sym)
    > > copy.instance_eval do
    > > @foo = @foo.send sym
    > > @bar = @bar.send sym
    > > end
    > >
    > > copy
    > > end
    > > end

    >
    > I like this, since it works on both 1.6 and 1.8.
    >
    > > but this does not work for #clone if the instance is frozen.

    >
    > Right, because the copy has already been frozen when we start to modify
    > it. I wonder if there's a good way around that (without using
    > initialize_copy, since that doesn't work on 1.6). Maybe I should
    > re-implement clone/dup in ruby (as you've done below) for Object so that
    > it has the same behavior as 1.8.


    Well, you could reimplement #clone in terms of #dup and apply all changes
    (freeze and taint) aftewards. You'll be quite likely to end up with a
    similar scheme to #initialize_copy, so the question is whether it's
    worthwile.

    > > This might be better:
    > >

    >
    > It's much better, though it seems a bit heavyweight.
    >
    > > class Foo
    > > attr_accessor :foo, :bar
    > >
    > > def clone; copy :clone; end
    > > def dup; copy :dup; end
    > >
    > > protected
    > > def copy(sym)
    > > c = self.class.new
    > >
    > > copy_init c
    > >
    > > c.freeze? if frozen
    > > c.taint if tainted?

    >
    > I think this should read:
    >
    > if sym == :clone then
    > c.freeze if frozen?
    > c.taint if tainted?
    > end


    Yes, of course! Thank you!

    > > c
    > > end
    > >
    > > def copy_init(c)
    > > c.instance_eval do
    > > @foo = @foo.sent sym
    > > @bar = @bar.sent sym
    > > end
    > > end
    > > end
    > >
    > > class Bar < Foo
    > > attr_accessor :name
    > >
    > > protected
    > > def copy_init(c)
    > > super
    > > c.instance_eval do
    > > @name = @name.send sym
    > > end
    > > end
    > > end
    > >
    > >
    > > > 4. When making the copy, is super the right way to make the copy, or
    > > > should allocate be used? (allocate doesn't work on 1.6.x, which I
    > > > still use heavily, though if it's the right solution, then it's

    the
    > > > right solution).

    > >
    > > I'm inclined to follow the Java clone() pattern and use super. Of

    course
    > > you can also just do self.class.new.

    >
    > The problem with self.class.new is that the initialize method may have
    > some semantics that are undesirable when copying.


    Exactly.

    > > > 5. Certain types cannot be dup'd (e.g. NilClass in all versions of

    Ruby
    > > > or Fixnum in 1.8 and later), so the above code will break if I

    write:
    > > >
    > > > Foo.new(10, 42).dup
    > > >
    > > > Is it possible (or is it even wise) to write a dup or clone

    function
    > > > that works with both value types and container types?

    > >
    > > It depends on whether you expect these types as members and what you

    want
    > > to be done with them. It's difficult to anwer this generally, that's

    why
    > > the automatic methods just do a shallow copy.
    > >
    > > Generally speaking IMHO NilClass#dup should return self, same for

    Fixnums
    > > and others. But OTOH I can see why Matz did it this way: so you get

    to
    > > know that some instance is not cloneable.

    >
    > That's exactly what I was thinking, though I was hoping for a better
    > answer.


    :)

    Maybe Matz has some other reason.

    Kind regards

    robert
     
    Robert Klemme, Jun 16, 2004
    #7
    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. Ben Charrow

    Idioms and Anti-Idioms Question

    Ben Charrow, Jun 22, 2009, in forum: Python
    Replies:
    11
    Views:
    541
    Lawrence D'Oliveiro
    Jul 4, 2009
  2. Alexey Verkhovsky
    Replies:
    1
    Views:
    122
    George Ogata
    Apr 3, 2004
  3. Robert Klemme
    Replies:
    5
    Views:
    205
  4. François Beausoleil

    :s.respond_to?(:dup) && :s.dup raises

    François Beausoleil, Apr 5, 2007, in forum: Ruby
    Replies:
    1
    Views:
    114
    Tim Hunter
    Apr 5, 2007
  5. Luka Stolyarov
    Replies:
    10
    Views:
    313
    Thomas Sondergaard
    Sep 11, 2010
Loading...

Share This Page