October 21, 2021·4 min read·engineeringperliterationsyntax

Perl 5.35.5 can iterate over several values at once

Experimental lexical iterator lists make pairs directly expressible. Their aliasing and padding rules matter as much as the shorter loop header.

A flat sequence of key/value pairs has a natural iteration width of two. Perl 5.35.5 introduces experimental loop syntax that can express that width directly, using a parenthesized list of lexical scalars after for my. The release notes show hash iteration with a key and value together. The same position in a loop header was previously a syntax error.

The notation saves more than an index calculation. It makes the relationship between adjacent elements part of the loop declaration. Its semantics still need attention, particularly when the input length is not divisible by the number of variables. The syntax manual shipped with this release specifies padding and aliasing, so the feature is more precise than an informal instruction to take the next pair.

I like this addition for small transformations where the data already arrives in pairs. The loop header states how the input is grouped without introducing another collection or a helper that hides the grouping rule. That is consistent with what makes Perl useful to me for text-processing tasks.

Lexical variables describe the iteration width

The new form accepts lexical scalar iterator variables. It does not accept every construct that would be valid on the left side of a list assignment. In particular, undef cannot stand in for an unwanted value. The manual describes this as a limitation of the current implementation, leaving any future change undecided.

use strict;
use warnings;
no warnings 'experimental::for_list';

my @fields = ("name", "worker", "state", "ready");
for my ($key, $value) (@fields) {
    print "$key=$value\n";
}

This input has two complete pairs. The first iteration supplies the first two elements and the second supplies the next two. The syntax is experimental and has its own warning category, experimental::for_list. Here the example suppresses that category explicitly. A feature declaration for some unrelated experiment is not what selects this form.

The release's documentation describes the syntax in terms of the intended 5.36 language, while the implementation is available here in the development series. That wording should not be read as an announcement that a later stable release has already shipped. October's concrete event is the addition to 5.35.5 and the opportunity to examine its documented behaviour.

An incomplete final group receives padding

When the input has too few elements for the last group, the excess iterator variables alias undef. The language acts as if enough undefined values were appended to complete the group. An array used as input does not actually grow as a result. Padding defines what the loop sees without changing the array's length.

That behaviour makes partial input representable, but does not validate its shape. A program requiring complete pairs still needs to reject an odd element count. Conversely, code that deliberately accepts a missing final value can handle undef in the body. These are application policies built on the same iteration mechanism.

The padding elements are not assignable values. Attempting to modify one fails, just as modifying a literal undefined value in an iterated list would fail. This matters if a loop body updates its iterator variables. A final variable bound to padding has different assignability from one aliasing an actual array element, even though both can be read.

A maintainer can therefore choose to extend an array explicitly before iteration if real writable elements are required. The manual names explicit extension and throwing an exception as alternatives when padding is unsuitable. Neither happens automatically. The documented default lets readers distinguish a short final group from a mutated input collection.

Hash iteration preserves value aliases

The manual's hash example adds another useful detail. The hash is copied into a flat list before iteration begins. Keys in that list are copies, but values are aliases. This is the same behaviour described for single-variable iteration over a hash expanded into a list. Binding a pair of names does not change the underlying distinction.

That means a loop that assigns to the value iterator can update a hash value, while assigning to the key iterator does not rename the hash key. The two names look symmetric in the header but have different relationships to the original hash. A description that calls both of them detached copies would misstate the contract.

The syntax consequently helps express the grouping without promising an immutable snapshot of every element. It also does not turn a hash into an ordered collection. Its useful guarantee is that each iteration obtains the adjacent key/value items from the flattened hash input. Applications needing a particular order still need to specify their own ordering operation.

I consider that aliasing detail essential to judging the addition. Brevity is helpful when the shorter form preserves behaviour that can be explained. The versioned documentation supplies enough detail to reason about mutation and incomplete input instead of treating the new header as shorthand whose corner cases are left to inference.

This development release also adds a dedicated t/op/for-many.t test file. The release notes identify it specifically with iteration over multiple values. The feature's immediate scope is a new experimental grammar for lexical iterator lists, with explicit padding for short final groups and the existing aliasing relationships of the input list.