Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Cannot assign to immutable value" when trying to assign to a string + role

Starting with the example in the Iterable doc page

role DNA does Iterable {
  method iterator(){ self.comb.iterator }
};

my @a does DNA = 'GAATCC';
.say for @a; # OUTPUT: «G␤A␤A␤T␤C␤C␤» 

I found it weird it's declared using the @, so I changed it to the natural way of declaring strings, $:

my $a does DNA = 'GAATCC';

But that fails with a somewhat bewildering "Cannot assign to an immutable value". No need to assign on the spot, so we can do:

my $a = 'GAATCC';
$a does DNA;
.say for $a;

Which just leaves mixing-in for later. But that just prints the string, without paying any attention to the Iterable mixin. Let's call it then explicitly:

.say for $a.iterator;

it does kinda the same thing as before, only it prints the value of $a.iterator, without actually calling the function:

<anon|69>.new

This looks like the same thing it's going on in this other question. Baseline question is I don't understand what role Iterable really does, and what for really does and when it is calling iterator on some object. Any idea?

like image 557
jjmerelo Avatar asked May 29 '18 17:05

jjmerelo


2 Answers

I don't think this line does what you think it does:

my @a does DNA = 'GAATCC';

It is the same as:

my @a := [ 'GAATCC', ];
@a does DNA;

Basically the .comb call coerces the array into a Str, and splits that into characters.


If you instead did this:

my @a = 'GAATCC' but DNA;

Which is basically the same as

my @a := Seq.new(('GAATCC' but DNA).iterator).Array;

Note that @ variables store Positional values not Iterable values.


The thing you want is

my $a = 'GAATCC' but DNA;

$a.map: &say;

If you want to be able to use for you can't use a variable with a $ sigil

my \a = 'GAATCC' but DNA;

.say for a;

You may want to add Seq list List etc methods to DNA.

role DNA does Iterable {
  method iterator(){
    self.comb.iterator
  }
  method Seq(){
    Seq.new: self.iterator
  }
  method list(){
    # self.Seq.list
    List.from-iterator: self.iterator
  }
}

my $a = 'GAATCC' but DNA;
.say for @$a;
like image 183
Brad Gilbert Avatar answered Oct 22 '22 11:10

Brad Gilbert


Your question's title points to a bug. This answer covers the bug and also other implicit and explicit questions you asked.

Background

fails with a somewhat bewildering "Cannot assign to an immutable value".

I think that's a bug. Let's start with some code that works:

my $a = 42;
say $a;          # 42
say WHAT $a;     # (Int)               type of VALUE currently ASSIGNED to $a
say WHAT VAR $a; # (Scalar)            type of VARIABLE currently BOUND to $a
$a = 42;         # works fine

In the my declaraton $a gets BOUND to a new Scalar container. A Scalar container normally hides itself. If you ask WHAT type $a is, you actually get the type of the value currently ASSIGNED to the Scalar (the value it "contains"). You need VAR to access the container BOUND to $a. When you assign with = to a Scalar container you copy the assigned value into the container.

role foo {}
$a does foo;     # changes the VALUE currently ASSIGNED to $a
                 # (NOT the VARIABLE that is BOUND to $a)
say $a;          # 42                  mixed in `foo` role is invisible
say WHAT $a;     # (Int+{foo})         type of VALUE currently ASSIGNED to $a
say WHAT VAR $a; # (Scalar)            type of VARIABLE currently BOUND to $a
$a = 99; say $a; # 99

The does mixes the foo role into the 42. You can still assign to $a because it's still bound to a Scalar.

Note how these two uses of does have very different effects:

my $a does foo;  # mixes `foo` into VARIABLE bound to $a
$a does foo;     # mixes `foo` into VALUE assigned to $a

The bug

$a.VAR does foo; # changes VARIABLE currently BOUND to $a (and it loses the 42)
say $a;          # Scalar+{foo}.new    VALUE currently ASSIGNED to $a
say WHAT $a;     # (Scalar+{foo})      type of VALUE currently ASSIGNED to $a
say WHAT VAR $a; # (Scalar+{foo})      type of VARIABLE currently BOUND to $a

$a = 'uhoh';     # Cannot assign to an immutable value

The does mixes the foo role into the Scalar bound to $a. It seems that a Scalar with a mixin no longer successfully functions as a container and the assignment fails.

This currently looks to me like a bug.

my $b does foo;  # BINDS mixed in VARIABLE to $b
$b = 'uhoh';     # Cannot assign to an immutable value

my $b does foo has the same result as my $b; $b.VAR does foo; so you get the same problem as above.

Other things you were confused about

my $a = 'GAATCC';
$a does DNA;
.say for $a;

just prints the string, without paying any attention to the Iterable mixin.

Because the $a VARIABLE is still bound to a Scalar (as explained in the Background section above), the VALUE that now has a DNA role mixed in is irrelevant per the decision process for uses about whether to call its argument's .iterator method.

Let's call it then explicitly ... prints the value of $a.iterator, without actually calling the function:

.say for $a.iterator;

Well it does call your DNA role's .iterator method. But that has another .iterator call at the end of the self.comb returned by your DNA role's iterator method so you're .saying that secondary .iterator.

Solutions that work today

I think Brad's answer nicely covers most of your options.

And I think your nice does DNA gist is as good as it gets with today's P6 if you want to use the $ sigil.

A solution that might one day work

In an ideal world all the sweetness in the P6 design would be fully realized in 6.c and the Rakudo compiler implementation of Perl 6. Perhaps that would include the ability to write this and get what you want:

class DNA is Scalar does Iterable { ... }
my $a is DNA = 'GAATCC';
.say for $a;

The ... code would be about the same as what you have in your gist except that the DNA class would be a scalar container, and so the new method would instead be a STORE method or similar that would assign the passed value to a $!value attribute or some such when a value was assigned to the container using =.

But instead, you get:

is trait on $-sigil variable not yet implemented. Sorry.

So the closest you can get today to the ideal of = 'string' to change $a is to bind using := DNA.new('string') as you did in your gist.

Note that you can bind arbitrary composite containers to @ and % sigil variables. So you can see how things are supposed to eventually work.

like image 7
raiph Avatar answered Oct 22 '22 12:10

raiph