Skip to content

Commit f22f090

Browse files
authored
Merge pull request #33 from haarg/map-with-topic
RFC for allowing alternate iteration variable in map and grep
2 parents fc9f312 + fdaccf4 commit f22f090

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed

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

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Map with different topic variable
2+
3+
## Preamble
4+
5+
Author: Graham Knop <[email protected]>
6+
ID: 0023
7+
Status: Exploratory
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+
### `grep my VAR BLOCK LIST`
76+
77+
This will evaluate `BLOCK` for each element of `LIST`, aliasing `VAR` to each
78+
element. Its behavior will otherwise match `grep BLOCK LIST`. It is only
79+
possible to use `my` variables for this.
80+
81+
### `map my (VAR, VAR) BLOCK LIST`
82+
83+
This will evaluate `BLOCK` for each set of two elements in `LIST`, aliasing
84+
the `VAR` to the first of each set, and the second `VAR` to the second. More
85+
than two variables can be used to iterate over sets of three or more items.
86+
87+
On the last iteration, there may not be enough elements remaining to fill
88+
every `VAR` slot. In this case, a warning will be issued, and the extra `VAR`
89+
slots will be filled with `undef`.
90+
91+
In scalar context, the return value will be the number of elements generated.
92+
93+
### `grep my (VAR, VAR) BLOCK LIST`
94+
95+
This will evaluate `BLOCK` for each set of two elements in `LIST`, aliasing
96+
the `VAR` to the first of each set, and the second `VAR` to the second. More
97+
than two variables can be used to iterate over sets of three or more items.
98+
99+
On the last iteration, there may not be enough elements remaining to fill
100+
every `VAR` slot. In this case, a warning will be issued, and the extra `VAR`
101+
slots will be filled with `undef`. The extra `undef` values used will also be
102+
included in the returned list.
103+
104+
In scalar context, this will return number of elements that would have been
105+
generated. This means it will always be a multiple of the count of `VAR`
106+
slots.
107+
108+
## Backwards Compatibility
109+
110+
As the syntax chosen is currently invalid, it should not present any backwards
111+
compatibility concerns.
112+
113+
While the parsing of `map` and `grep` is complex and has ambiguities, the
114+
additions proposed are bounded and simple to parse, so they do not introduce
115+
any new ambiguities.
116+
117+
## Security Implications
118+
119+
None foreseen.
120+
121+
## Examples
122+
123+
```perl
124+
my @my_array = (11 .. 14);
125+
126+
my @trad_map = map { $_ + 1 } @my_array;
127+
# ( 12, 13, 14, 15 )
128+
129+
my @new_map = map my $f { $f + 1 } @my_array;
130+
# ( 12, 13, 14, 15 )
131+
132+
my @pair_map = map my ($f, $g) { $f + $g } @my_array;
133+
# ( 23, 27 )
134+
135+
my @new_grep = grep my $f { $f > 12 } @my_array;
136+
# ( 13, 14 )
137+
138+
my %my_hash = (
139+
first_key => 11,
140+
second_key => 14,
141+
);
142+
143+
my %pair_grep = grep my ($f, $g) { $f =~ /sec/ } %my_hash;
144+
# ( second_key => 14 )
145+
```
146+
147+
## Prototype Implementation
148+
149+
- List::Util includes the functions `pairmap` and `pairgrep` which can do
150+
2-at-a-time iteration. These use the "magic" `$a` and `$b` variables.
151+
152+
## Future Scope
153+
154+
- Doing n-at-a-time iteration is useful, but authors may not have a use for
155+
all of the variables, especially with `grep`. Could the extra variables be
156+
eliminated? Maybe `grep my ($key, undef) { ... } ...` could be supported?
157+
158+
- `map`, `grep`, and even `for` could possibly be extended to allow
159+
refaliasing syntax for their variables.
160+
161+
```perl
162+
my %hash = (
163+
key1 => [ 1, 2, 3 ],
164+
key2 => [ 4, 5, 6 ],
165+
);
166+
my %out = map my ($key, \@values) { ... } %hash;
167+
```
168+
169+
- Subs with a `(&@)` prototype are, in part, meant to allow mimicking the
170+
syntax of `map` and `grep`. An example of this being `List::Util::any`:
171+
172+
```perl
173+
use List::Util qw(any);
174+
175+
my $found = any { /ab/ } qw( 1234 abcd );
176+
```
177+
178+
It would be desirable to be able to to write a perl function that accepted
179+
the same syntax this this PPC proposes.
180+
181+
## Rejected Ideas
182+
183+
- `my` is required because the behavior of `for` when used with non-`my` or
184+
predeclared variables is confusing and hard to explain. The newer syntax
185+
`for my ($var1, $var2) { ... }` also requires `my`, both for that reason
186+
as well as implementation details. Making the `my` implicit also
187+
introduces a difference from `for` making it harder to document and
188+
explain.
189+
190+
## Open Issues
191+
192+
Nothing at this time.
193+
194+
## References
195+
196+
- Pre-PPC discussion: https://www.nntp.perl.org/group/perl.perl5.porters/2022/11/msg265104.html
197+
198+
## Copyright
199+
200+
Copyright (C) 2022, Graham Knop
201+
202+
This document and code and documentation within it may be used, redistributed
203+
and/or modified under the same terms as Perl itself.

0 commit comments

Comments
 (0)