Why can readonly array attributes on a Raku class be modified?

Viewed 163

Why can readonly array attributes be modified on a Raku class, but on the other hand, scalars cannot be modified?

How can I make @.baz "readonly"?

class Boo {
    has $.bar;
    has @.baz;
};

my $boo = Boo.new;

$boo.baz = (1, 2); # works ... ?
say $boo.baz;

$boo.bar = 1; #fails ... expected

My rakudo version:

This is Rakudo version 2020.05.1 built on MoarVM version 2020.05
implementing Raku 6.d.
2 Answers

The baz attribute is read only.

The thing is that the baz attribute is an Array which has mutable elements.


When you call $boo.baz you basically get a reference to the array.

Once you have a reference to the array you can do anything that you could normally do to the array.

say $boo.baz.VAR.name; # @!baz

When you assign to an array, what you are really doing is telling the array that it will have new contents.

my @a = ('a','b','c');
say @a.WHICH; # Array|94070988080608

@a = (1, 2, 3);
say @a.WHICH; # Array|94070988080608

Notice that the .WHICH doesn't change. That is because it is still the same array. It just has new contents.

The exact same thing happens when you assign to a public array attribute.
You are not assigning a new array, you are altering the existing one.

All of the behaviours of a variable are not intrinsic to the variable, they are instead handled by an object.
In the case of arrays the object that handles the assignment is Array.


The simplest fix is to just overload the autogenerated accessor method.

class Boo {
    has $.bar;
    has @.baz;

    method baz () { @!baz.List }
}

If you never plan on changing the values in @!baz you could make it a List itself

class Boo {
    has $.bar;
    has @.baz is List;
}

My vote goes to the 'is List' version suggested by @BradGilbert ... I tried it with defaults and got this Awesome Error message:

===SORRY!=== Error while compiling ...
Defaults on compound attribute types not yet implemented. Sorry.
Workaround: Create/Adapt TWEAK method in class Boo, e.g:

    method TWEAK() {
        @!baz := (initial values) unless @!baz;
    }
Related