Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to assign the .lines Seq to a variable and iterate over it?

Tags:

raku

Assigning an iterator to variable changes apparently how the Seq behaves. E.g.

use v6;

my $i = '/etc/lsb-release'.IO.lines;
say $i.WHAT;
say '/etc/lsb-release'.IO.lines.WHAT;
.say for $i;
.say for '/etc/lsb-release'.IO.lines;

results in:

(Seq)
(Seq)
(DISTRIB_ID=Ubuntu DISTRIB_RELEASE=18.04 DISTRIB_CODENAME=bionic DISTRIB_DESCRIPTION="Ubuntu 18.04.1 LTS")
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.1 LTS"

So once assigned I get only the string representation of the sequence. I know I can use .say for $i.lines to get the same output but I do not understand the difference between the assigned and unassigned iterator/Seq.

like image 309
matthias krull Avatar asked Sep 28 '18 15:09

matthias krull


2 Answers

Assignment in Perl 6 is always about putting something into something else.

Assignment into a Scalar ($ sigil) stores the thing being assigned into a Scalar container object, meaning it will be treated as a single item; this is why for $item { } will not do an iteration. There are various ways to overcome this; the most conceptually simple way is to use the <> postfix operator, which strips away any Scalar container:

my $i = '/etc/lsb-release'.IO.lines;
.say for $i<>;

There's also the slip operator ("flatten into"), which will achieve the same:

my $i = '/etc/lsb-release'.IO.lines;
.say for |$i;

Assignment into an Array will - unless the right-hand side is marked lazy - iterate it and store each element into the Array. Thus:

my @i = '/etc/lsb-release'.IO.lines;
.say for @i;

Will work, but it will eagerly read all the lines into @i before the loop starts. This is OK for a small file, but less ideal for a large file, where we might prefer to work lazily (that is, only pulling a bit of the file into memory at a time). One might try:

my @i = lazy '/etc/lsb-release'.IO.lines;
.say for @i;

But that won't help with the retention problem; it just means the array will be populated lazily from the file as the iteration takes place. Of course, sometimes we might want to go through the lines multiple times, in which case assignment into an Array would be the best choice.

By contrast, declaring a symbol and binding it to that:

my \i = '/etc/lsb-release'.IO.lines;
.say for i;

Is not a "put into" operation at all; it just makes the symbol i refer to exactly what lines returns. This is rather clearer than putting it into a Scalar container only to take it out again. It's also a little easier on the reader, since a my \foo = ... can never be rebound, and so the reader doesn't need to be on the lookup for any potential changes later on in the code.

As a final note, it's worth knowing that the my \foo = ... form is actually a binding, rather than an assignment. Perl 6 allows us to write it with the = operator rather than forcing :=, even if in this case the semantics are := semantics. This is just one of a number of cases where a declaration with an initializer differs a bit from a normal assignment, e.g. has $!foo = rand actually runs the assignment on every object instantiation, while state $foo = rand only runs it only if we're on the first entry to the current closure clone.

like image 116
Jonathan Worthington Avatar answered Nov 02 '22 00:11

Jonathan Worthington


If you want to be able to iterate over the sequence you need to either assign it to a positional :

my @i = '/etc/lsb-release'.IO.lines; .say for @i;

Or you can tell the iterator that you want to treat the given thing as iterable :

.say for @$i

Or you can Slip it into a list for the iterator :

.say for |$i

like image 20
Scimon Proctor Avatar answered Nov 02 '22 01:11

Scimon Proctor