5

I've come across this code at rosettacode

my @pascal = [1], { [0, |$_ Z+ |$_, 0] } ... Inf;
.say for @pascal[^4];
# ==>
# [1]
# [1 1]
# [1 2 1]
# [1 3 3 1]

Inside the explicit generator block, I know how the individual operators like the list flattening | and the zip operator Z+ work but I have difficulty comprehending how they cooperate to generate the next array. Can somebody explain in detail how it works? Thank you.

Note: The code is slightly rearranged for brevity, i.e. it's superficially different than the one in Rosetta.

Lars Malmsteen
  • 738
  • 4
  • 23

2 Answers2

5

This is a somewhat interesting application of the sequence operator, in so far as the values that it produces each time are arrays. So:

  • [1] is the first array produced at the start of the sequence
  • The block indicates how to produce the next array in the sequence. It takes one argument, which is the previous sequence value
  • The Inf will never be matched, so the sequence will go on forever

A simpler example may be helpful: the sequence [1], [1,1], [1,1,1], .... That is, we want to produce an array that is the previous item in the sequence with an extra 1 at the end. We could do this:

my @ones = [1], { [|$_, 1] } ... Inf;
.say for @ones[^4];

Producing:

[1]
[1 1]
[1 1 1]
[1 1 1 1]

The first time the block is called, it gets [1] in $_. We slip that with |, thus producing an array [1, 1]. That is passed to the block the second time, and so forth.

So, breaking down [0, |$_ Z+ |$_, 0]. The outer square brackets are an array composer. The rest can be read as 0, |$_ zipped with |$_, 0, using the + operator to zip the things.

The first call to be block will pass [1], thus we will be zipping 0, 1 and 1, 0. Zipping with an operator applies it pairwise, meaning it will calculate 0 + 1, 1 + 0. The overall result is the array [1,1].

The second call to the block gets that [1,1]. It forms the lists 0, 1, 1 and 1, 1, 0, and then the zip operator forms the pairwise addition again, which is 0 + 1, 1 + 1, 1 + 0. The overall result is the array [1,2,1].

Effectively, then, the result grows an element each time, resulting from the pairwise addition of the previous result padded with a zero at either end.

Jonathan Worthington
  • 29,104
  • 2
  • 97
  • 136
  • Thank you for the detailed and enlightening answer. It shows step by step how the `[` `]` s are used to produce new arrays inside the code generator block. All in all it's as useful and comprehensive as the @Scimon 's answer. It helped me to solidify how those operators inside the code block work to produce the Pascal's sequence. – Lars Malmsteen Nov 21 '19 at 16:59
5

So let's take a look at what's going on.

First up the Sequence generator ... this takes a list of starting values, a code block itself and and end point.

It uses the starting values to generate each next item in the list so lets start with @pascal[0] that's simple enough : [1]

For @pascal[1] we call the code block with our value in @pascal[0] like so :

sub ( $array ) { 
  [0, |$array Z+ |$array, 0] 
}

You'll note I've made it into a sub, this is just so I can explain things easier. In the anonymous code block $_ is the incoming data and I've called it $array. So what code to we run when $array == [1]?

[0, |[1] Z+ |[1], 0] => [1,1] 

So here's a question what happens if we don't use the | ?

[0, [1] Z+ [1], 0] => [1,1] 

It's the same! So whys it there? Well what happens if we set $array == [3]?

[0, [3] Z+ [3], 0] => [1,1] 

Weird? No, because the Z+ transforms like this :

[0, [3] Z+ [3], 0] => [0 + [3], [3] + 0] => [1,1] 

Z makes a new list by zipping the elements with the given operator + between them. + is doing a numerical computation and the Numerical representation of an Array is the number of elements in it, in this case 1 both times.

And this is where the | slip operator comes in, it slips the array it's given open merging into the list context it's in. So lets go back to @pascal[1]

[0, |[1] Z+ |[1], 0] => [0, 1 Z+ 1, 0] => [0 + 1, 1 + 0] => [1,1] 

Ok.. So @pascal[2] is calling the same block but now passing in [1,1]

[0, |[1, 1] Z+ |[1, 1], 0] => [0, 1, 1 Z+ 1, 1, 0] => [0 + 1, 1 + 1, 1 + 0] => [1,2,1] 

And so on into Infinity!

I hope that's helped to explain what's going on?

Scimon Proctor
  • 4,558
  • 23
  • 22
  • Thanks a lot for the comprehensive answer. It was those commas inside the `[` `]` which were misleading. I took them for kind of commas which displayed the progression of the sequence whereas they were nothing more than the plain old commas which separated the array elements from each other. Of course I came to this realization after I looked at the successive invocations of the `sub` :) – Lars Malmsteen Nov 21 '19 at 16:35