Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions ppcs/ppc0034-signature-refalias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Ref-aliased parameters in subroutine signatures

## Preamble

Author: Gianni Ceccarelli <[email protected]>
Sponsor:
ID: 0034
Status: Draft

## Abstract

Allow declaring ref-aliased parameters in subroutine signatures.

## Motivation

As of Perl version 5.42, we can do:

use v5.42;
use experimental 'declared_refs';

sub foo {
my (\%a) = @_;
...
}

but we cannot do:

sub foo(\%a) {
...
}

## Rationale

Subroutine signatures work very similarly to the old-style `my … =
@_;` line, including the slurpy parameter at the end. But, as noted
above, they don't allow declaring a ref-aliased parameter. This feels
inconsistent.

## Specification

In addition to the form `$things`, a scalar parameter may also be
declared as ref-alias (requires the `refaliasing` feature and the
`declared_refs` feature, see "Assigning to References" and "Declaring
a Reference to a Variable" in perlref for more details):

sub go_over(\@things) { say "Oooh, $_!" for @things }

This subroutine must still be called with a scalar value, but the
value must now be a reference to an array. Equivalently:

sub look_at(\%these) {
say "$_ means $these{$_}" for sort keys %these;
}

must be called with a reference to a hash, and:

sub normalise(\$string) {
$string = lc($string);
}

must be called with a reference to a scalar. As with normal scalar
parameters, ref-aliased parameters can be ignored:

sub ignore(\@) { ... }

and have default values:

sub walk($cb, \@nodes, \%seen ||= {}) {
for my $node (@nodes) {
next if $seen{$node->id}++;
$cb->($node);
walk($cb, $node->children, \%seen);
}
}

The last example is equivalent to:

sub walk($cb, $nodes, $seen ||= {}) {
for my $node ($nodes->@*) {
next if $seen->{$node->id}++;
$cb->($node);
walk($cb, $node->children, $seen);
}
}

with different legibility trade-offs.

Notice that arguments are still passed by reference, so any
modification to their contents will be seen by the caller. In other
words:

sub my_push(\@items, $new_one) {
push @items, $new_one;
}

works exactly the same way as:

sub my_push($items, $new_one) {
push $items->@*, $new_one;
}

or

sub my_push {
my (\@items, $new_one) = @_;
push @items, $new_one;
}

## Backwards Compatibility

The proposed syntax is currently not valid, so it can not generate
confusion for existing code.

`B::Deparse` will need to be made aware of it.

`PPI` can parse it just fine:

sub foo(\@a=[])

produces:

PPI::Statement::Sub
PPI::Token::Word 'sub'
PPI::Token::Whitespace ' '
PPI::Token::Word 'foo'
PPI::Structure::Signature ( ... )
PPI::Statement::Expression
PPI::Token::Cast '\'
PPI::Token::Symbol '@a'
PPI::Token::Operator '='
PPI::Structure::Constructor [ ... ]

`Perl::Critic` seems to have no problems with it: given the above
declaration, it complains that «Magic variable "@a" should be assigned
as "local"»; it does the same if I write `sub foo($a=[])`.

## Security Implications

None foreseen.

## Examples

`LWP::Protocol::http::hlist_remove` looks like this:

sub hlist_remove {
my($hlist, $k) = @_;
$k = lc $k;
for (my $i = @$hlist - 2; $i >= 0; $i -= 2) {
next unless lc($hlist->[$i]) eq $k;
splice(@$hlist, $i, 2);
}
}

It could become:

sub hlist_remove(\@hlist, $k) {
$k = lc $k;
for (my $i = @hlist - 2; $i >= 0; $i -= 2) {
next unless lc($hlist[$i]) eq $k;
splice(@hlist, $i, 2);
}
}

## Prototype Implementation

Paul Evans implemented most of this already:
https://metacpan.org/pod/Sublike::Extended#Refalias-Parameters
only missing the default value assignments.

## Future Scope

Perl 5.44 will support named parameters in signatures. It would make
sense to also allow ref-aliased named parameters; the obvious syntax
would be `:\%foo`, which may look too much "punctuation soup" (thanks
Paul for that expression).

## Rejected Ideas

Paul Evans suggested using an attribute instead:

sub foo (%a :refalias)

which would extend to named parameters:

sub foo (:%a :refalias)

and be similar to a proposed "aliased scalars" feature:

sub foo ($a :alias) { $a=1 }
# equivalent to:
sub foo { $_[0]=1 }

One of the problems with the attribute-based approach is that `sub
foo(%a)` and `sub foo(%a :refalias)` have very different signatures
(the first one takes an even-sized list, the second one takes a single
scalar), but they look very similar.

Everywhere else in Perl, `%a` and `@a` imply multiple values / list
context, but `\%a` and `\@a` are scalars / induce scalar context. I
believe we should not break this pattern.

## Open Issues

How do we handle ref-aliased named parameters?

## Copyright

Copyright (C) 2025, Gianni Ceccarelli.

This document and code and documentation within it may be used,
redistributed and/or modified under the same terms as Perl itself.