Search code examples
clonedeep-copyrakushallow-copy

How to make a separate copy of an object in Perl 6?


I don't fully understand the docs, so I've tried clone, and it seems if there is an attribute of a mutable class, it can be changed in the new object using the old one (and that's what I don't want). How to make them (i.e. the copy and the original) fully separate?

class A {
  has @.a;
}

my A $x = A.new;
my A $y = A.new;

$x.a = 1, 2;

$y = $x.clone;

$x.a.push(4);
say $y.a; # [1 2 4]

Solution

  • The default clone inherited from Mu is shallow, as documented. This means that it will only copy the object itself, but not anything the object references. It is possible to override clone to have your preferred semantics, which is probably the best thing to do here.

    One useful thing to know in doing this is that clone takes named parameters and uses them to assign to properties of the cloned object. This is worth knowing because:

    • One should make sure to handle this when overriding clone, to avoid surprises to users of the overridden clone method who use this feature
    • One can use this when overriding clone to succinctly opt in to cloning of a particular array or hash attribute

    Thus for the case in the question, writing:

    class A {
        has @.a;
        method clone() {
            callwith(:@!a, |%_)
        }
    }
    

    Will result in the output [1 2] as presumably desired. How does it work?

    • The |%_ just passes on any tweaks the caller of this clone method specified
    • :@!a is short for a => @!a
    • callwith calls the inherited clone (from Mu in this case)
    • Assignment, not binding, semantics are used on @!a in the target object (just as during object construction), resulting in a copy of the array

    This shortcut works for hash attributes too. For an attribute containing another object, it'd look like callsame(x => $!x.clone).