Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assign a Seq(Seq) into arrays

Tags:

raku

What is it the correct syntax to assign a Seq(Seq) into multiple typed arrays without assign the Seq to an scalar first? Has the Seq to be flattened somehow? This fails:

class A { has Int $.r }

my A (@ra1, @ra2);

#create two arrays with 5 random numbers below a certain limit

#Fails: Type check failed in assignment to @ra1; expected A but got Seq($((A.new(r => 3), A.n...)
(@ra1, @ra2) =
   <10 20>.map( -> $up_limit {
        (^5).map({A.new( r => (^$up_limit).pick ) })
    });
like image 858
LuVa Avatar asked Jun 05 '19 20:06

LuVa


2 Answers

TL;DR Binding is faster than assignment, so perhaps this is the best practice solution to your problem:

:(@ra1, @ra2) := <10 20>.map(...);

While uglier than the solution in the accepted answer, this is algorithmically faster because binding is O(1) in contrast to assignment's O(N) in the length of the list(s) being bound.

Assigning / copying

Simplifying, your non-working code is:

(@listvar1, @listvar2) = list1, list2;

In Raku infix = means assignment / copying from the right of the = into one or more of the container variables on the left of the =.

If a variable on the left is bound to a Scalar container, then it will assign one of the values on the right. Then the assignment process starts over with the next container variable on the left and the next value on the right.

If a variable on the left is bound to an Array container, then it uses up all remaining values on the right. So your first array variable receives both list1 and list2. This is not what you want.


Simplifying, here's Christoph's answer:

@listvar1, @listvar2 Z= list1, list2;

Putting the = aside for a moment, Z is an infix version of the zip routine. It's like (a physical zip pairing up consecutive arguments on its left and right. When used with an operator it applies that operator to the pair. So you can read the above Z= as:

@listvar1 = list1;
@listvar2 = list2;

Job done?


Assignment into Array containers entails:

  • Individually copying as many individual items as there are in each list into the containers. (In the code in your example list1 and list2 contain 5 elements each, so there would be 10 copying operations in total.)

  • Forcing the containers to resize as necessary to accommodate the items.

  • Doubling up the memory used by the items (the original list elements and the duplicates copied into the Array elements).

  • Checking that the type of each item matches the element type constraint.

Assignment is in general much slower and more memory intensive than binding...

Binding

:(@listvar1, @listvar2) := list1, list2;

The := operator binds whatever's on its left to the arguments on its right.

If there's a single variable on the left then things are especially simple. After binding, the variable now refers precisely to what's on the right. (This is especially simple and fast -- a quick type check and it's done.)

But that's not so in our case.

Binding also accepts a standalone signature literal on its left. The :(...) in my answer is a standalone Signature literal.

(Signatures are typically attached to a routine without the colon prefix. For example, in sub foo (@var1, @var2) {} the (@var1, @var2) part is a signature attached to the routine foo. But as you can see, one can write a signature separately and let Raku know it's a signature by prefixing a pair of parens with a colon. A key difference is that any variables listed in the signature must have already been declared.)

When there's a signature literal on the left then binding happens according to the same logic as binding arguments in routine calls to a receiving routine's signature.

So the net result is that the variables get the values they'd have inside this sub:

sub foo (@listvar1, @listvar2) { }
foo list1, list2;

which is to say the effect is the same as:

@listvar1 := list1;
@listvar2 := list2;

Again, as with Christoph's answer, job done.

But this way we'll have avoided assignment overhead.

like image 167
raiph Avatar answered Nov 02 '22 08:11

raiph


Not entirely sure if it's by design, but what seems to happen is that both of your sequences are getting stored into @ra1, while @ra2 remains empty. This violates the type constraint.

What does work is

@ra1, @ra2 Z= <10 20>.map(...);
like image 40
Christoph Avatar answered Nov 02 '22 06:11

Christoph