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

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 }

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.)

Tags:

Raku

Seq