Idioms for dup/clone

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

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)?

For 1.6 Kernel#dup is implemented via Kernel#clone

1.8 use #initialize_copy

Guy Decoux

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

"Paul Brannan" <pbrannan@atdesk.com> schrieb im Newsbeitrag
news:20040614142913.GV2788@atdesk.com...

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

"ts" <decoux@moulon.inra.fr> schrieb im Newsbeitrag
news:200406141449.i5EEnps24286@moulon.inra.fr...

> 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)?

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

ts wrote:

....

> 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)?

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

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

···

On Tue, Jun 15, 2004 at 12:08:37AM +0900, Robert Klemme wrote:

"Paul Brannan" <pbrannan@atdesk.com> schrieb im Newsbeitrag
news:20040615171413.GY2788@atdesk.com...

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

:slight_smile:

Maybe Matz has some other reason.

Kind regards

    robert

···

On Tue, Jun 15, 2004 at 12:08:37AM +0900, Robert Klemme wrote: