What is the difference in contexts in nested and non-nested maps in Perl 6?

The content of every program file, block, subroutine, etc. is a "statement list" consisting of semicolon-separated1 statements. With that in mind:

  1. Statements that get sunk:

    • All but the final statement in a statement list.
    • Any loop statement (for, while, etc.2) at statement-list level3, even when it is the final one.
    • Any statement in the top-level statement list of a program or module file, even when it is the final one.4
  2. Statements that get returned instead of sunk:

    • The final statement in a statement list, except for the cases mentioned above.

Sinking forces eager evaluation, returning doesn't.

Example

In your case, the first map statement is in the middle of a statement list, so it gets sunk.

But the nested map statement is the final statement of its statement list, so its result gets returned in the form of a not-yet-iterated Seq.

Its parent map statement is also a final statement, but it's in the top-level statement list of the program file, so it gets sunk, causing it to eagerly iterate the sequence that consists of three Seq values. (Insert a say statement before the inner map, to see this.)
But nothing sinks, or otherwise iterates, each of those three inner Seq values.

From the design docs

More verbosely, from Synopsis 04, line 664:

In any sequence of statements, only the value of the final statement is returned, so all prior statements are evaluated in sink context, which is automatically eager, to force the evaluation of side effects. (Side effects are the only reason to execute such statements in the first place, and Perl will, in fact, warn you if you do something that is "useless" in sink context.) A loop in sink context not only evaluates itself eagerly, but can optimize away the production of any values from the loop.

The final statement of a statement list is not a sink context, and can return any value including a lazy list. However, to support the expectations of imperative programmers (the vast majority of us, it turns out), any explicit loop found as the final statement of a statement list is automatically forced to use sink semantics so that the loop executes to completion before returning from the block.

This forced sink context is applied to loops only at the statement list level, that is, at the top level of a compilation unit, or directly inside a block. Constructs that parse a single statement or semilist as an argument are presumed to want the results of that statement, so such constructs remain lazy even when that statement is a loop.


1) When a closing brace } appears as the last proper token of a line, as in
my @a = @b.map: { $_ + 1 } # whitespace/comment doesn't count
it also ends the current statement, but otherwise a semicolon is needed to separate statements.

2) map doesn't count, because it is a function and not a loop keyword.

3) Meaning that when a loop statement appears in a different place than directly in a statement list, e.g.
lazy for ^10 { .say } # as argument to a keyword expecting a single statement
(for ^10 { .say }) # inside an expression
then it isn't sunk by default. That's what the last paragraph of the synopsis quote is trying to say.

UPDATE: This doesn't seem to actually the case in Rakudo, but that may be a bug.

4) This rule isn't mentioned in the synopsis, but it's how it works in Rakudo, and I'm pretty sure it's intentional.


.map basically returns a .Seq. What happens is that the inner map returns a Seq to the outer map, but since the results of that map are sunk, they disappear without being iterated on.

If you say the outer map, you will pull the result of the inner map, and you will see the result of the .Seq the inner map returned:

my @array = (1, 2), (3, 4), ('a', 'b');
say "---Inside another map:";
say @array.map: {
    my @item = 1, 2, 3;
    @item.map: {
        say $_;
        }
    }
---Inside another map:
1
2
3
1
2
3
1
2
3
((True True True) (True True True) (True True True))

Hope that made sense :-)

An alternate solution would be to add a specific return value to the outer map. Then the inner map would be sunk, and therefore iterate, like so:

my @array = (1, 2), (3, 4), ('a', 'b');
say "---Inside another map:";
say @array.map: {
    my @item = 1, 2, 3;
    @item.map: {
        say $_;
        }
    42   # make sure ^^ map is sunk
    }
---Inside another map:
1
2
3
1
2
3
1
2
3