Skip to content

Commit fc9f312

Browse files
authored
Merge pull request #54 from leonerd/signature-named-params
Signature named params
2 parents ae18e70 + 5e40fa0 commit fc9f312

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Named Parameters in Signatures
2+
3+
## Preamble
4+
5+
Author: Paul Evans <PEVANS>
6+
Sponsor:
7+
ID: 0024
8+
Status: Exploratory
9+
10+
## Abstract
11+
12+
Adds the ability for subroutine signatures to process named arguments in the form of name-value pairs passed by the caller, in a fashion familiar to existing uses of assignment into a hash.
13+
14+
## Motivation
15+
16+
Perl 5.20 added "subroutine signatures", native syntax for more succinctly expressing the common patterns of processing arguments inbound into a subroutine by unpacking the `@_` array. Rather than writing common styles of code directly, a more compact notation, in a style immediately familiar to users of many other languages, allows Perl to create the required behaviour from that specification.
17+
18+
```perl
19+
sub f ($x, $y, $z = 123) { ... }
20+
21+
# instead of
22+
sub f {
23+
die "Too many arguments" if @_ > 3;
24+
die "Too few arguments" if @_ < 2;
25+
my ($x, $y, $z) = @_;
26+
$z = 123 if @_ < 3;
27+
...
28+
}
29+
```
30+
31+
One common form of argument processing involves passing an even-sized list of key/value pairs and assigning that into a hash within the subroutine, so that specifically named parameters can be extracted from it. There is currently no support from subroutine signature syntax to assist authors in providing such behaviours.
32+
33+
## Rationale
34+
35+
(explain why the (following) proposed solution will solve it)
36+
37+
## Specification
38+
39+
A new kind of element may be present in a subroutine signature, which consumes a named argument from the caller. These elements are written with a leading colon prefix (`:$name`), indicating that it is named rather than positional. The name of each parameter is implied by the name of the lexical into which it is assigned, minus the leading `$` sigil.
40+
41+
Each element provides a new lexical variable that is visible during the body of the function, in the same manner as positional ones.
42+
43+
The value of a named parameter is taken from the argument values passed by the caller, in a manner familiar to existing uses of hash assignment. The caller should pass an even-sized name-value pair list. The values corresponding to names of parameters will be assigned into the variables. The order in which the values are passed by the caller is not significant.
44+
45+
Since it is a relatively common pattern in callsites in existing code to rely on the semantics of assignment of name-value pair lists into hashes, the beahviour on encountering duplicate key names needs to be preserved. This is that duplicated key names do not raise an error or a warning, and simply accept the last value associated with that name. This allows callers to collect values from multiple sources with different orders of priority to override them; for example using a hash of values combined with individual elements:
46+
47+
```perl
48+
sub func ( :$abc, :$xyz, ... ) { ... }
49+
50+
func(
51+
abc => 123,
52+
%args,
53+
xyz => 789,
54+
);
55+
```
56+
57+
In this example, the given `abc` value will take effect unless overridden by a later value in `%args`, but the given `xyz` value will replace an earlier one given in `%args`. Neither situation will result in a warning or error.
58+
59+
Furthemore, all of the new behaviour is performed within the body of the invoked subroutine entirely by inspecting the values of the arguments that were passed. The subroutine is not made aware of how those values came to be passed in - whether from literal name-value syntax, a hash or array variable expansion, or any other expression yielding such a list of argument name and value pairs.
60+
61+
```perl
62+
sub make_colour ( :$red, :$green, :$blue ) { ... }
63+
64+
make_colour( red => 1.0, blue => 0.5, green => 0.2 );
65+
# The body of the function will be invoked with
66+
# $red = 1.0
67+
# $green = 0.2
68+
# $blue = 0.5
69+
```
70+
71+
As with positional parameters, a named parameter without a defaulting expression is mandatory, and an error will be raised as an exception if the caller fails to pass a corresponding value. A defaulting expression may be specified using any of the operators available to positional parameters - `=`, `//=` or `||=`.
72+
73+
```perl
74+
sub make_colour ( :$red = 0, :$green = 0, :$blue = 0 ) { ... }
75+
76+
make_colour( red => 1.0, blue => 0.5 );
77+
# The body of the function will be invoked with
78+
# $red = 1.0
79+
# $green = 0
80+
# $blue = 0.5
81+
```
82+
83+
Since defaulting expressions are full expressions and not necessarily simple constant values, the time at which they are evaluated is significant. Much like with positional parameters, each is evaluated in order that it is written in source, left to right, and each can make use of values assigned by earlier expressions. This means they are evaluated in the order that is written in the function's declaration, which may not match the order that values were passed from the caller in the arguments list.
84+
85+
A subroutine is permitted to use a combination of *mandatory* positional and named parameters in its definition, provided that all named parameters appear after all the positional ones. Any named parameters may be optional; there are no ordering constraints here.
86+
87+
If a subroutine uses named parameters then it may optionally use a slurpy hash argument as its final element. In this case, the hash will receive all *other* name-value pairs passed by the caller, apart from those consumed by named parameters. If the subroutine does not use a slurpy argument, then it will be an error raised as an exception for there to be any remaining name-value pairs after processing.
88+
89+
While all of the above has been specified in terms of subroutines, every point should also apply equally to the methods provided by the `class` feature, after first processing the implied invocant `$self` parameter.
90+
91+
## Backwards Compatibility
92+
93+
At the site of a subroutine's definition, this specification only uses new syntax in the form of the leading colon prefix on parameter names. Such syntax was formerly invalid in previous versions of perl. Thus there is no danger of previously-valid code being misinterpreted.
94+
95+
All of the behaviour provided by this new syntax is compatible with and analogous to any existing code that could have been written prior, perhaps by direct assignment into a hash. There are no visible differences in the external interface to a subroutine using such syntax, and it remains callable in exactly the same way. Functions provided by modules could be upgraded to use the new syntax without any impact on existing code that invokes them.
96+
97+
## Security Implications
98+
99+
There are no anticipated security concerns with expanding the way that subroutines process parameters in this fashion.
100+
101+
## Examples
102+
103+
As the intention of this syntax addition is to make existing code practice more consise and simple to write, it would be illustrative to compare pairs of functions written in the newly proposed vs. the existing style.
104+
105+
One example comes from [`IPC::MicroSocket::Server`](https://metacpan.org/pod/IPC::MicroSocket::Server). This is currently using the `Sublike::Extended` module (see "Prototype Implementation" below) to provide the syntax. The actual module also uses `Object::Pad` to provide the `method` keyword; this example is paraphrased to avoid that. This module requires a `path` named argument, and optionally takes a `listen` argument, defaulting its value to 5 if the caller did not provide a defined value.
106+
107+
```perl
108+
extended sub new_unix ( $class, :$path, :$listen //= 5 )
109+
{
110+
...
111+
}
112+
```
113+
114+
This replaces the previous version of the code which handled arguments by the more traditional approach of assigning into a hash:
115+
116+
```perl
117+
sub new_unix ( $class, %args )
118+
{
119+
my $path = $args{path};
120+
my $listen = $args{listen} // 5;
121+
...
122+
}
123+
```
124+
125+
Already the new code is shorter and more consise. Additionally it contains error checking that complains about missing mandatory keys, or unrecognised keys, which the previous version of the code did not include.
126+
127+
Another example, this time from [`Text::Treesitter::QueryCursor`](https://metacpan.org/pod/Text::Treesitter::QueryCursor). This method takes an optional argument named `multi`, tested for truth. If absent it should default to false.
128+
129+
```perl
130+
sub next_match_captures ( $self, :$multi = 0 )
131+
{
132+
...
133+
}
134+
```
135+
136+
The previous version of this code did include complaints about unrecognised keys, and was rather longer because of it:
137+
138+
```perl
139+
sub next_match_captures ( $self, %options )
140+
{
141+
my $multi = delete $options{multi};
142+
keys %options and
143+
croak "Unrecognised options to ->next_captures: " . join( ", ", keys %options );
144+
145+
...
146+
}
147+
```
148+
149+
## Prototype Implementation
150+
151+
The [`XS::Parse::Sublike`](https://metacpan.org/pod/XS::Parse::Sublike) module contains parsing to allow third-party syntax modules to parse subroutine-like constructions, and includes a parser for named parameters already having this syntax. These are also made available to regular subroutines via the `extended` keyword provided by [`Sublike::Extended`](https://metacpan.org/pod/Sublike::Extended).
152+
153+
Additionally, other existing CPAN modules already parse syntax in this, or a very similar format:
154+
155+
* [`Function::Parameters`](https://metacpan.org/pod/Function::Parameters)
156+
157+
* [`Kavorka`](https://metacpan.org/dist/Kavorka/view/lib/Kavorka/Manual/Signatures.pod)
158+
159+
* [`Method::Signatures`](https://metacpan.org/pod/Method::Signatures)
160+
161+
All of these use the leading-colon syntax in a signature declaration to provide named parameters in the same style as this proposal. It would appear we are in good company here.
162+
163+
## Future Scope
164+
165+
* If the Metaprogramming API (PPC0022) gains introspection abilities to enquire about subroutine signature parameters, further consideration will need to be made in that API on how to represent the extra kinds of parameters added by this specification.
166+
167+
## Rejected Ideas
168+
169+
* This specification intentionally makes no changes to the call-site of any subroutines using named parameters.
170+
171+
## Open Issues
172+
173+
### Parameter Names
174+
175+
How to specify a named parameter whose name is anything other than the name of the lexical variable into which its value is assigned? This is more of note when considering that traditional argument handling techniques involving assignment into hashes can handle more styles of name that would be invalid for lexical variables - for example, names including hyphens.
176+
177+
Perhaps a solution would be to permit attributes on parameter variables, then define a `:name` attribute for this purpose.
178+
179+
```perl
180+
sub display ( $message, :$no_colour :name(no-colour) ) {
181+
...
182+
}
183+
```
184+
185+
Further thoughts in that direction suggested that this could also support multiple names with aliases:
186+
187+
```perl
188+
sub display ( $message, :$no_colour :name(no-colour no-color) ) {
189+
...
190+
}
191+
```
192+
193+
The attribute syntax starting with a leading colon does visiually look quite similar to the named parameter syntax which also uses. This is a little unfortunate, but perhaps an inevitable consequence of the limited set of characters available from ASCII. As attributes are already well-established as leading with a colon, the only other option would be to pick a different character for named attributes; but this would be contrary to the established convention of the existing modules listed above.
194+
195+
Perl does not currently support attributes being applied to parameters in signatures, named or otherwise, but this is the subject of a future PPC document I am currently drafting. I will add a reference here when it is published.
196+
197+
## Copyright
198+
199+
Copyright (C) 2024, Paul Evans.
200+
201+
This document and code and documentation within it may be used, redistributed and/or modified under the same terms as Perl itself.

0 commit comments

Comments
 (0)