Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl6 one liner execution. How is the topic updated?

Executing the one liner to process CSV a line at a time from stdin:

perl6 -ne 'my @a; $_.split(",").kv.map: {@a[$^k]+=$^v}; say @a; ENTER {say "ENTER"}; BEGIN {say "BEGIN"}; LEAVE {say "LEAVE"}; END {say "END"}';

Typing in:

1,1
1,1
^D

Gives the following output:

BEGIN
ENTER
1,1
[1 1]
1,1
[2 2]
LEAVE
END

Here we can see that the one liner is not a block executed multiple times as the ENTER and LEAVE phaser are only executed once.

This makes sense as the variable @a is accumulating. If the one liner was a block the value of @a would be reset each time.

My question is how does the topic variable $_ get updated? The topic variable is a Str (at least that's what $_.^name says). How does its value update without re-entering the block?

What am I missing?

like image 847
drclaw Avatar asked Jan 25 '19 00:01

drclaw


3 Answers

When you add -n it adds a for loop around your code.

You think it adds one like this:

for lines() {
  # Your code here
}

The compiler just adds the abstract syntax tree nodes for looping without actually adding a block.

(
   # Your code here
) for lines()

(It could potentially be construed as a bug.)

To get it to work like the first one:

(             # -n adds this

  -> $_ {     # <-- add this

              # Your code here

  }( $_ )     # <-- add this

) for lines() # -n adds this

I tried just adding a bare block, but the way the compiler adds the loop causes that to not work.


In general ENTER and LEAVE are scoped to a block {}, but they are also scoped to the “file” if there isn't a block.

ENTER say 'ENTER file';
LEAVE say 'LEAVE file';
{
  ENTER say '  ENTER block';
  LEAVE say '  LEAVE block';
}
ENTER file
  ENTER block
  LEAVE block
LEAVE file

Since there is no block in your code, everything is scoped to the “file”.

like image 124
Brad Gilbert Avatar answered Sep 18 '22 22:09

Brad Gilbert


The -n command line argument puts a loop around your program,

for $*ARGFILES.lines {
    # Program block given on command line
}

whereas the program execution phasers you used (BEGIN and END), are run once either at compile time or after the program block has finished, so they will not be part of the loop at run time.

The ENTER block phaser will run at every block entry time, whereas the the LEAVE block phaser will run at every block exit time. So these phasers will be run for each line read in the for loop.

like image 41
Håkon Hægland Avatar answered Sep 17 '22 22:09

Håkon Hægland


Update -- Rakudo 2020.10

Running your original accumulator code (using the -ne linewise flag) gives the following result. Note how the word "final" appears in every line:

~$ perl6 -ne 'my @a; $_.split(",").kv.map: {@a[$^k]+=$^v}; say @a, " final"; ENTER {say "ENTER"}; BEGIN {say "BEGIN"}; LEAVE {say "LEAVE"}; END {say "END"};' drclaw.txt
BEGIN
ENTER
[1 1] final
[2 3] final
[3 6] final
LEAVE
END

Below, running essentially duplicate scripts back-to-back with the -ne flag gives an interesting result. BEGIN, ENTER,LEAVE, and END show up in the exact same location, duplicated on the order of once-per-call:

~$ perl6 -ne 'my @a; .split(",").kv.map: {@a[$^k]+=$^v}; say @a, " final_a";  ENTER {say "ENTER"}; BEGIN {say "BEGIN"}; LEAVE {say "LEAVE"}; END {say "END"};  my @b; .split(",").kv.map: {@b[$^k]+=$^v}; say @b, " final_b"; ENTER {say "ENTER"}; BEGIN {say "BEGIN"}; LEAVE {say "LEAVE"}; END {say "END"};' drclaw.txt
BEGIN
BEGIN
ENTER
ENTER
[1 1] final_a
[1 1] final_b
[2 3] final_a
[2 3] final_b
[3 6] final_a
[3 6] final_b
LEAVE
LEAVE
END
END

However, removing the -ne flag below lets you run a for lines() {...} loop within the Raku code itself (single script, not duplicated back-to-back). This result seems more in line with what you were expecting:

~$ perl6 -e 'my @a; for lines() {.split(",").kv.map: {@a[$^k]+=$^v};}; say @a, " final"; ENTER {say "ENTER"}; BEGIN {say "BEGIN"}; LEAVE {say "LEAVE"}; END {say "END"};' drclaw.txt
BEGIN
ENTER
[3 6] final
LEAVE
END

I think the short answer to your questions is that Phasers respect Block/Loop semantics, but are limited script-wise as to how many times they will report back to the implementer (apparently only once per call). But the ultimate difference is that the return to the user is linewise for the -ne command line flag, as compared to an internal for lines() {...} loop sans the -ne command line flag.

Finally, you can always force the reloading of the $_ topic variable with the andthen infix operator. Maybe this is what you were looking for all along:

~$ perl6 -e 'my @a; for lines() {.split(",").kv.map: {@a[$^k]+=$^v} andthen $_.say }; say @a, " final"; ENTER {say "ENTER"}; BEGIN {say "BEGIN"}; LEAVE {say "LEAVE"}; END {say "END"};' drclaw.txt
BEGIN
ENTER
(1 1)
(2 3)
(3 6)
[3 6] final
LEAVE
END

[Test file under analysis, below].

~$ cat drclaw.txt
1,1
1,2
1,3

https://docs.raku.org/language/operators#index-entry-andthen

like image 30
jubilatious1 Avatar answered Sep 17 '22 22:09

jubilatious1