aes has asked for the wisdom of the Perl Monks concerning the following question:

Greetings. In Perl6, I'd like to create a lazy list of code blocks...
my @foo = sub ($x,$y) { state $n = 0; $x*$y*$n++ } ... *;
...but when I try to use it...
my $f = @foo[0]; say $f.(2,9);
...I get the error:
Not enough positional parameters passed; got 0 but expected 2
Any ideas how to fix or rethink this? Thanks.

Replies are listed 'Best First'.
Re: Perl6 lazy list of code blocks
by moritz (Cardinal) on May 22, 2012 at 18:32 UTC

    I can explain why this error occurs. The sequence operator ... tries to invoke your closure, but since you didn't provide any elements to start with, it tries to invoke the closure without arguments, and fails.

    The solution is probably to not use the sequence operator for this kind of thing.

    Based on what you have written, I guess that this is what you want to achieve:

    use v6; my @blocks := (0..*).map: -> $n { -> $x, $y { $x * $y * $n } }; say @blocks[2](2, 9);

    It doesn't make much sense to me, but it is an infinite list of closures, each one having a higher $n than the previous one.

    Update: An approach that is closer to your original is to use the list repetition operator:

    sub gen($x, $y) { state $n = 0; $x*$y*$n++ } my @foo := &gen xx *;

    But that probably doesn't do what you want. Since merely creating the elements of the infinite list doesn't invoke the subroutine, $n is incremented in the order that the closures are called, not in the order they appear inside the list. In the example above, $n is shared between all references to &gen, so it will always increase by one. If you write it as

    my @foo := sub ($x, $y) { state $n = 0; $x*$y*$n++ } xx *;

    instead, each closure gets a separate $n, so calling

    say @foo[2](1, 1); say @foo[0](1, 1); say @foo[5](1, 1); say @foo[0](1, 1);

    Produces

    0 0 0 1

      Thank you, moritz, for your explanation, and in the process, for introducing me to several aspects of Perl6 I was unfamiliar with.

      I am indeed seeking to build an indefinite list of closures, each one having an incremented value of $n. If this seems an odd request, then rest assured it's only because I've rendered this as an isolated, simplified problem for posting here; the more complex application uses an analogous data structure in an elemental way.

      Your first suggestion...

      my @blocks := (0..*).map: -> $n { -> $x, $y { $x * $y * $n } };

      ...seems just right, but it hangs rakudo-star-2012.04.

      Your second suggestion...

      sub gen($x, $y) { state $n = 0; $x*$y*$n++ } my @foo := &gen xx *;

      ...is, as you suggested, not what I'm looking for, because I want $n to reflect the ordinal position in the series, not the ordinal call order of the closure.

      Your third suggestion...

      my @foo := sub ($x, $y) { state $n = 0; $x*$y*$n++ } xx *;

      ...gives every element of @foo the same ordinal position in the series (0), and re-accessing @foo[0], say, increments $n for @foo[0]'s closure, whereas I want it to be static indicator of ordinal position in the series.

      Given what you said about the sequence operator (...) trying to invoke my closure, I realized that what I need is the equivalent of prime (') in CL/Scheme, and to properly scope the ordinal incrementor $n.

      So this is my solution...

      my @foo = { state $n=-1; $n++; sub ($x,$y) { $x+$y+$n } } ... *;
      ...which when accessed in the following manner...
      for 0..2 -> $i { for 0..2 { say @foo[$i](3,5); } say; }
      ...will produce the following output...
      8 8 8 9 9 9 10 10 10
        Your first suggestion...
        my @blocks := (0..*).map: -> $n { -> $x, $y { $x * $y * $n } };
        ...seems just right, but it hangs rakudo-star-2012.04.

        It does not hang on my machine, also tested with rakudo star 2012.04.

        One way it could possibly hang on yours is if you use it in the interactive shell (REPL). Because that tries to get a string representation of the return value (here the lazy list), and that doesn't work for an infinite list. It would be nicer to not hang, but detecting if a lazy list is infinite is a tricky business.

        If you still want to use the REPL, you can just but another statement after it on the same line:

        > my @blocks := (0..*).map: -> $n { -> $x, $y { $x * $y * $n } }; 1; 1

        That way the 1 at the end is converted to a string, not the infinite list.

        I found a bug. Using...

        my @foo = { state $n=-1; $n++; sub ($x,$y) { $x+$y+$n } } ... *;

        ...in my sequential access code example from my previous post, the state variable $n seemed to maintain the ordinal position of each closure in the series, but it hid the bug of $n being instead a high-water mark for positional access, and so...

        my $foo_0 = @foo[0].(3,5); my $foo_1 = @foo[1].(3,5); my $foo_2 = @foo[2].(3,5);

        ...produces...

        10 10 10

        ...when what I desired was...

        8 9 10

        Thus, after an additional scope trick to lexically package (in $p) the persistent state value (in $n), this is my better solution...

        my @foo = { state $n=-1; $n++; my $p=$n; sub ($x,$y) { $x+$y+$p } } .. +. *;