Skip to content

Commit 236b9ba

Browse files
authored
Merge pull request #75 from dakkar/refalias-sigs
PPC0034: refalias in subroutine signatures
2 parents 209b515 + e2fe494 commit 236b9ba

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed

ppcs/ppc0034-signature-refalias.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Ref-aliased parameters in subroutine signatures
2+
3+
## Preamble
4+
5+
Author: Gianni Ceccarelli <[email protected]>
6+
Sponsor:
7+
ID: 0034
8+
Status: Draft
9+
10+
## Abstract
11+
12+
Allow declaring ref-aliased parameters in subroutine signatures.
13+
14+
## Motivation
15+
16+
As of Perl version 5.42, we can do:
17+
18+
use v5.42;
19+
use experimental 'declared_refs';
20+
21+
sub foo {
22+
my (\%a) = @_;
23+
...
24+
}
25+
26+
but we cannot do:
27+
28+
sub foo(\%a) {
29+
...
30+
}
31+
32+
## Rationale
33+
34+
Subroutine signatures work very similarly to the old-style `my … =
35+
@_;` line, including the slurpy parameter at the end. But, as noted
36+
above, they don't allow declaring a ref-aliased parameter. This feels
37+
inconsistent.
38+
39+
## Specification
40+
41+
In addition to the form `$things`, a scalar parameter may also be
42+
declared as ref-alias (requires the `refaliasing` feature and the
43+
`declared_refs` feature, see "Assigning to References" and "Declaring
44+
a Reference to a Variable" in perlref for more details):
45+
46+
sub go_over(\@things) { say "Oooh, $_!" for @things }
47+
48+
This subroutine must still be called with a scalar value, but the
49+
value must now be a reference to an array. Equivalently:
50+
51+
sub look_at(\%these) {
52+
say "$_ means $these{$_}" for sort keys %these;
53+
}
54+
55+
must be called with a reference to a hash, and:
56+
57+
sub normalise(\$string) {
58+
$string = lc($string);
59+
}
60+
61+
must be called with a reference to a scalar. As with normal scalar
62+
parameters, ref-aliased parameters can be ignored:
63+
64+
sub ignore(\@) { ... }
65+
66+
and have default values:
67+
68+
sub walk($cb, \@nodes, \%seen ||= {}) {
69+
for my $node (@nodes) {
70+
next if $seen{$node->id}++;
71+
$cb->($node);
72+
walk($cb, $node->children, \%seen);
73+
}
74+
}
75+
76+
The last example is equivalent to:
77+
78+
sub walk($cb, $nodes, $seen ||= {}) {
79+
for my $node ($nodes->@*) {
80+
next if $seen->{$node->id}++;
81+
$cb->($node);
82+
walk($cb, $node->children, $seen);
83+
}
84+
}
85+
86+
with different legibility trade-offs.
87+
88+
Notice that arguments are still passed by reference, so any
89+
modification to their contents will be seen by the caller. In other
90+
words:
91+
92+
sub my_push(\@items, $new_one) {
93+
push @items, $new_one;
94+
}
95+
96+
works exactly the same way as:
97+
98+
sub my_push($items, $new_one) {
99+
push $items->@*, $new_one;
100+
}
101+
102+
or
103+
104+
sub my_push {
105+
my (\@items, $new_one) = @_;
106+
push @items, $new_one;
107+
}
108+
109+
## Backwards Compatibility
110+
111+
The proposed syntax is currently not valid, so it can not generate
112+
confusion for existing code.
113+
114+
`B::Deparse` will need to be made aware of it.
115+
116+
`PPI` can parse it just fine:
117+
118+
sub foo(\@a=[])
119+
120+
produces:
121+
122+
PPI::Statement::Sub
123+
PPI::Token::Word 'sub'
124+
PPI::Token::Whitespace ' '
125+
PPI::Token::Word 'foo'
126+
PPI::Structure::Signature ( ... )
127+
PPI::Statement::Expression
128+
PPI::Token::Cast '\'
129+
PPI::Token::Symbol '@a'
130+
PPI::Token::Operator '='
131+
PPI::Structure::Constructor [ ... ]
132+
133+
`Perl::Critic` seems to have no problems with it: given the above
134+
declaration, it complains that «Magic variable "@a" should be assigned
135+
as "local"»; it does the same if I write `sub foo($a=[])`.
136+
137+
## Security Implications
138+
139+
None foreseen.
140+
141+
## Examples
142+
143+
`LWP::Protocol::http::hlist_remove` looks like this:
144+
145+
sub hlist_remove {
146+
my($hlist, $k) = @_;
147+
$k = lc $k;
148+
for (my $i = @$hlist - 2; $i >= 0; $i -= 2) {
149+
next unless lc($hlist->[$i]) eq $k;
150+
splice(@$hlist, $i, 2);
151+
}
152+
}
153+
154+
It could become:
155+
156+
sub hlist_remove(\@hlist, $k) {
157+
$k = lc $k;
158+
for (my $i = @hlist - 2; $i >= 0; $i -= 2) {
159+
next unless lc($hlist[$i]) eq $k;
160+
splice(@hlist, $i, 2);
161+
}
162+
}
163+
164+
## Prototype Implementation
165+
166+
Paul Evans implemented most of this already:
167+
https://metacpan.org/pod/Sublike::Extended#Refalias-Parameters
168+
only missing the default value assignments.
169+
170+
## Future Scope
171+
172+
Perl 5.44 will support named parameters in signatures. It would make
173+
sense to also allow ref-aliased named parameters; the obvious syntax
174+
would be `:\%foo`, which may look too much "punctuation soup" (thanks
175+
Paul for that expression).
176+
177+
## Rejected Ideas
178+
179+
Paul Evans suggested using an attribute instead:
180+
181+
sub foo (%a :refalias)
182+
183+
which would extend to named parameters:
184+
185+
sub foo (:%a :refalias)
186+
187+
and be similar to a proposed "aliased scalars" feature:
188+
189+
sub foo ($a :alias) { $a=1 }
190+
# equivalent to:
191+
sub foo { $_[0]=1 }
192+
193+
One of the problems with the attribute-based approach is that `sub
194+
foo(%a)` and `sub foo(%a :refalias)` have very different signatures
195+
(the first one takes an even-sized list, the second one takes a single
196+
scalar), but they look very similar.
197+
198+
Everywhere else in Perl, `%a` and `@a` imply multiple values / list
199+
context, but `\%a` and `\@a` are scalars / induce scalar context. I
200+
believe we should not break this pattern.
201+
202+
## Open Issues
203+
204+
How do we handle ref-aliased named parameters?
205+
206+
## Copyright
207+
208+
Copyright (C) 2025, Gianni Ceccarelli.
209+
210+
This document and code and documentation within it may be used,
211+
redistributed and/or modified under the same terms as Perl itself.

0 commit comments

Comments
 (0)