How can I pass a sequence as a parameter in Perl 6?
Asked Answered
O

3

10

In Perl 6, I can iterate a literal sequence:

.say for 0 ... 3;

I can bind to a scalar and iterate that:

my $s := 0 ... 3;
.say for $s;

But I can't bind to a scalar, pass it as an argument, and then iterate that:

my $t := 0 ... 3;
show( $t );

sub show ( $seq ) { .say for $seq }

The subroutine gets a single element of type Seq, but it doesn't iterate it.

0
1
2
3
0
1
2
3
(0 1 2 3)

Does something in the process of preparing the parameter iterate through everything already?

Orpah answered 21/11, 2016 at 5:19 Comment(0)
R
11

A value held in a Scalar container is not automatically iterated

While both $s and $seq are scalars (aka "variables"), $s is bound directly to a Seq value whereas your $seq is bound to an intermediary Scalar (note uppercase S) "container" that in turn contains the Seq. A value held in a Scalar container is not automatically iterated when used with features like for.


In more detail:

my $s := 0 ... 3;
.say for $s;

Because the my variable declaration uses the direct binding operator := for initialization, $s is directly bound to the single Seq value 0 ... 3.

This means the for statement sees a single Seq value, determines that it does the Iterable role, and flattens (iterates) it.

Now consider this:

my $s := 0 ... 3;
my $container = $s;
.say for $container;

Because the second my declaration uses the assignment operator = for initialization, the new variable $container is first bound to a new Scalar container which then "contains" whatever gets assigned.

In keeping with the language wide Slurpy Conventions (in particular: "An Iterable inside a Scalar container does not count"), a for statement does not iterate a value held in a Scalar container, so the .say for $container line only does one say.

A similar situation applies for your original show routine because variable parameter declarations are (semantically) containers by default.

One option is to instead add an is raw trait to your $seq parameter:

sub show ( $seq is raw ) { .say for $seq }

This prevents the usual automatic binding of $seq to a Scalar container (that in turn would contain the Seq value) as part of the call to show.

Another option is to let $seq be bound to a Scalar container but explicitly flatten (iterate) the $seq variable in the body of the show routine using a prefix |:

sub show ( $seq ) { .say for |$seq }
Ricks answered 21/11, 2016 at 9:11 Comment(0)
W
7

Instead of the is raw parameter trait suggested by raiph, you can also use a sigil-less variable as the parameter, which doesn't introduce a Scalar container:

sub show (\seq) { .say for seq }

(You can also use this form for normal variables, as in my \a = 5; say a;, but note that they are single-assignment only.)

A variation of this is the + form, which passes on the raw argument if it is an Iterable (like a List or Seq), but when a non-iterable argument is passed it promotes it to a List of one element, so that the body of the function can rely on always getting an Iterable:

sub show (+seq) { .say for seq }

(This is what most of the built-in list processing routines like grep and zip use.)

Of course if you prefer to use a $ parameter that introduces a Scalar container, you can just "decontainerize" it again before iterating over it, by calling the .list method on it:

sub show ($seq) { .say for $seq.list }  # explicit

sub show ($seq) { .say for @$seq }      # short-hand syntax

(Update: Eh, .list actually turns the Seq into a List, i.e. it won't be memory-efficient in case of a large Seq. Using |$seq like you already discovered in your own answer, doesn't have this problem.)

Woodenware answered 21/11, 2016 at 11:28 Comment(2)
Thanks! But yeah, I'm thinking about infinite lazy sequences. :)Orpah
just to mention the decont op <> viz. docs.raku.org/routine/%3C%3ESpringclean
S
0

From the docs ...

sub foo($bar) { say $bar }     # $bar is a parameter 
foo(42);                       # 42 is an argument

Also here ...

Sigil Binds to  Default behavior
$   Scalar      Generate new Scalar, use instead of Scalar in argument, if any
@   Positional  Bind directly to the argument
@   PositionalBindFailover  If binding failed, call argument's .cache method, bind to result
%   Associative Bind directly to the argument
&   Callable    Bind directly to the argument
\   (anything)  Bind directly to the argument, keep existing Scalar, if any

With Sigil $ the caller Scalar container is distinct from the callee Scalar container and the call does an implicit assignment. [Nevertheless '\' and 'is raw' are ways to disable this and to force binding if that is what you prefer.]

I gather that the intent is to have containers be local to blocks by default to help memory separation and parallelization. All other Sigils bind directly - but they are amenable to parallelization in other ways.

So the Sigil $ carries implications both for Iterability and for Separation.

Springclean answered 26/8, 2020 at 20:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.