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