Skip to content

Commit c780f36

Browse files
committed
PPC for allowing alternate iteration variable in map and grep
1 parent fc9f312 commit c780f36

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

ppcs/ppc0033-map-grep-with-topic.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Map with different topic variable
2+
3+
## Preamble
4+
5+
Author: Graham Knop <[email protected]>
6+
ID: 0033
7+
Status: Draft
8+
9+
## Abstract
10+
11+
Allow `map` and `grep` to be given a different variable to be used as the topic
12+
variable, rather than using `$_`. Also allow multiple variables to be given to
13+
do n-at-a-time iteration.
14+
15+
## Motivation
16+
17+
Traditionally, `map` and `grep` loop over a list, aliasing `$_` to each list
18+
entry. While this can be convenient in many cases, it gets awkward when nested
19+
maps are needed. You need to manually save the `$_` value in another variable.
20+
21+
```perl
22+
my %hash = (
23+
foo => [ 1, 2, 3, 4 ],
24+
bar => [ 5, 6, 7, 8 ],
25+
);
26+
27+
my @out = map {
28+
my $key = $_;
29+
map { "$key: $_" } $hash{$key}->@*;
30+
} keys %hash;
31+
```
32+
33+
Using `$_` can also be dangerous if you need to call code not under your
34+
direct control, due to it being the implicit target of operations like
35+
`readline`.
36+
37+
It would be more convenient if you could use a different variable for the
38+
topic, similar to what `for` allows.
39+
40+
```perl
41+
my @out = map my $key {
42+
map my $i { "$key: $i" } $hash{$key}->@*;
43+
} keys %hash;
44+
```
45+
46+
A natural extension of this syntax would be to allow n-at-a-time iteration, as
47+
`for` can do on perl 5.36+.
48+
49+
```perl
50+
my @out = map my ($key, $val) {
51+
map my $i { "$key: $i" } $val->@*;
52+
} %hash;
53+
```
54+
55+
All of this would apply to `grep` as well.
56+
57+
As the syntax proposed is currently invalid, a feature should not be needed to
58+
enable it.
59+
60+
## Rationale
61+
62+
The syntax chosen is meant to follow from the syntax of `for`, treating `map`
63+
as "`for` as an expression". The chosen syntax does not create any
64+
ambiguities, and naturally extends to follow the syntax used by `for` for
65+
n-at-a-time iteration.
66+
67+
## Specification
68+
69+
### `map my VAR BLOCK LIST`
70+
71+
This will evaluate `BLOCK` for each element of `LIST`, aliasing `VAR` to each
72+
element. Its behavior will otherwise match `map BLOCK LIST`. It is only
73+
possible to use `my` variables for this.
74+
75+
### `map my (VAR, VAR) BLOCK LIST`
76+
77+
This will evaluate `BLOCK` for each set of two elements in `LIST`, aliasing
78+
the `VAR` to the first of each set, and the second `VAR` to the second. If
79+
there is not a multiple of two items in `LIST`, the last run of `BLOCK` will
80+
assign `undef` to the extra `VAR` slot not corresponding to an element in
81+
`LIST`. More than two variables can be used to iterate over sets of three or
82+
more items.
83+
84+
## Backwards Compatibility
85+
86+
As the syntax chosen is currently invalid, it should not present any backwards
87+
compatibility concerns.
88+
89+
While the parsing of `map` and `grep` is complex and has ambiguities, the
90+
additions proposed are bounded and simple to parse, so they do not introduce
91+
any new ambiguities.
92+
93+
## Security Implications
94+
95+
None foreseen.
96+
97+
## Examples
98+
99+
```perl
100+
my @my_array = (11 .. 14);
101+
102+
my @trad_map = map { $_ + 1 } @my_array;
103+
# ( 12, 13, 14, 15 )
104+
105+
my @new_map = map my $f { $f + 1 } @my_array;
106+
# ( 12, 13, 14, 15 )
107+
108+
my @pair_map = map my ($f, $g) { $f + $g } @my_array;
109+
# ( 23, 27 )
110+
111+
my @new_grep = grep my $f { $f > 12 } @my_array;
112+
# ( 13, 14 )
113+
114+
my %my_hash = (
115+
first_key => 11,
116+
second_key => 14,
117+
);
118+
119+
my %pair_grep = grep my ($f, $g) { $f =~ /sec/ } %my_hash;
120+
# ( second_key => 14 )
121+
```
122+
123+
## Prototype Implementation
124+
125+
- List::Util includes the functions `pairmap` and `pairgrep` which can do
126+
n-at-a-time iteration. These use the "magic" `$a` and `$b` variables.
127+
128+
## Future Scope
129+
130+
- Doing n-at-a-time iteration is useful, but authors may not have a use for
131+
all of the variables, especially with `grep`. Could the extra variables be
132+
eliminated? Maybe `grep my ($key, undef) { ... } ...` could be supported?
133+
134+
- `map`, `grep`, and even `for` could possibly be extended to allow
135+
refaliasing syntax for their variables.
136+
137+
```perl
138+
my %hash = (
139+
key1 => [ 1, 2, 3 ],
140+
key2 => [ 4, 5, 6 ],
141+
);
142+
my %out = map my ($key, \@values) { ... } %hash;
143+
```
144+
145+
- Subs with a `(&@)` prototype are, in part, meant to allow mimicking the
146+
syntax of `map` and `grep`. An example of this being `List::Util::any`:
147+
148+
```perl
149+
use List::Util qw(any);
150+
151+
my $found = any { /ab/ } qw( 1234 abcd );
152+
```
153+
154+
It would be desirable to be able to to write a function that accepted the
155+
same syntax this this PPC proposes.
156+
157+
## Rejected Ideas
158+
159+
- `my` is required because the behavior of `for` when used with non-`my` or
160+
predeclared variables is confusing and hard to explain. The newer syntax
161+
`for my ($var1, $var2) { ... }` also requires `my`, both for that reason
162+
as well as implementation details. Making the `my` implicit also
163+
introduces a difference from `for` making it harder to document and
164+
explain.
165+
166+
## Open Issues
167+
168+
Nothing at this time.
169+
170+
## References
171+
172+
- Pre-PPC discussion: https://www.nntp.perl.org/group/perl.perl5.porters/2022/11/msg265104.html
173+
174+
## Copyright
175+
176+
Copyright (C) 2022, Graham Knop
177+
178+
This document and code and documentation within it may be used, redistributed
179+
and/or modified under the same terms as Perl itself.

0 commit comments

Comments
 (0)