Front page | perl.perl5.porters |
Postings from November 2019
Query Parameters
Thread Previous
|
Thread Next
From:
Dave Mitchell
Date:
November 28, 2019 17:03
Subject:
Query Parameters
Message ID:
20191128170235.GC3573@iabyn.com
=head2 Synopsis:
?$x peek ahead to the next arg
??$x peek ahead and see if there is a next arg
?@a peek ahead and copy all remaining args
?%h peek ahead and copy all remaining key/val arg pairs
?{ code } execute some code without consuming any args
Sometimes you want to find out some extra information about the arguments
and the state of argument processing. With @_ available, this can be
done, if messily; if @_ isn't populated (see the "@_ Suppression" thread),
then it becomes harder/impossible, unless some other mechanisms are added.
Such things include:
* For an optional argument, distinguishing between whether an undefined arg
was supplied, or an arg wasn't supplied at all; e.g. in:
sub get_or_set($self, $val = undef) { ... }
how do you distinguish between these two:
$foo->get_or_set();
$foo->get_or_set(undef);
* Passing the original arguments to a superclass method after shifting
the invocant, but ignoring default values; e.g. the equivalent of:
sub foo {
my $self = shift;
my ($x,$y) = @_;
return $self->SUPER::foo(@_) if $x + $y > 0;
}
Query parameters is what I propose as a general mechanism to provide an
equivalent facility to the now unavailable @_.
Normally, each parameter element in the signature introduces a new lexical
variable, in some fashion binds that variable to the next argument, and
at the same time I<consumes> that argument (i.e. removes it from the list
of arguments remaining to to processed).
I propose that if a parameter element begins with '?' it becomes a "query
parameter". It still typically introduces a new lexical variable, but that
variable is set to the value of some query about the current state of
argument processing, I<without> any arguments being consumed. It is
effectively peeking ahead.
Note that '?' is not part of any sigil - it is the first character of the
parameter element; for example if we decide to allow typed lexicals, then
you would write
sub f (? Int $argc, .....) { ... }
not
sub f (Int ?$argc, .....) { ... }
This is a general escape mechanism, with the possibility to add whatever
new syntax we like following the '?'; but for now I propose the following
four forms. Note that most of these peek at the sorted/deduplicated arg
list (i.e. the one generated for named arguments), except ?@a, which uses
the original raw list.
=head2 Boolean query parameter
This is of the form ??$foo. It sets the specified parameter variable to a
boolean value indicating whether an appropriate argument is available to
bind to the next (non-query) parameter. For example:
sub foo($self, ??$has_x, $x = 0, ??$also_y, $y = 0) { ... }
$p->foo(); # $has_x false, $also_y false
$p->foo(100); # $has_x true, $also_y false
$p->foo(100,200); # $has_x true, $also_y true
Before a named parameter, it indicates that at least one available
name-value argument pair matches the name of that parameter:
sub foo(??$has_x, :$x = 0,
??$has_y, :$y = 0,
??$has_z, :$z = 0)
{ ... }
foo(y => 100); # $has_x false, $has_y true, $has_z false
Before a slurpy parameter, it indicates that at least one argument is left
to populate it.
[ I did think about making ??$foo an integer value rather than just a
boolean, indicating how many arguments are left to consume. But this
doesn't work well before a named parameter, because there may be arguments
left (so return a positive integer), yet none of those arguments happen to
pair with the next name (so return a false value). ]
=head2 Scalar query parameter
This is of the form ?$foo. The parameter $foo will be bound to the next
argument, but without consuming it. It behaves the same as ??$foo in terms
of what argument it examines. If no suitable argument is available, it
will be set to undef. It may have a default value.
For example in the following, $self and $all[0] will have the same
value:
sub foo(?$self, @all) { bar(@all) if $self->yes }
# like:
sub foo { my $self = $_[0]; bar(@_) if $self->yes }
=head2 Array query parameter
This is of the form ?@foo. @foo will be set to any remaining arguments,
without consuming them. So it behaves like a slurpy array, but doesn't
have to be the last parameter. For example:
sub foo($self, ?@point, $x = 0, $y = 0, $z = 0) {
$self->bar(@point) if $x + $y + $z >= 1;
}
$p->foo(1); # calls $p->bar(1), not $p->bar(1, 0, 0);
Unlike the other query types, this one always examines the raw argument
list, (i.e. before being sorted for named parameters). Because of this,
an array query parameter is forbidden from appearing anywhere to the right
of any named parameter.
Note that the query array can use any extra syntax which is applicable to
slurpy arrays; for example '*' indicates that the elements of the array
are aliased rather than copied (see the "Aliasing and Read-only variables"
thread). So in particular,
sub foo(? *@args, ......) { ... } # @args now simulates @_
=head2 Hash query parameter
This is most useful in the presence of named parameters. As discussed in
the "Named Parameters" thread, in the presence of named parameters, the
argument list first goes through a conceptual sorting process before being
applied to parameters in left-to-right order. Any arguments beyond the
positional parameters are treated as (name, value) pairs, and are stable
sorted in order of named parameters, with any unrecognised names going to
the end. Duplicate recognised names discard any earlier pairs.
A hash query parameter acts like a hash slurpy, but without consuming any
arguments. It is applied against the remaining part of the (conceptually)
sorted argument list, (compare that to an array query parameter, which is
applied against the original raw unsorted list). For example, in:
sub foo (
$p1, $p2, # positional parameters
:$n1, :$n2, # named parameters
? %qslurpy, # query parameter
:$n3 = 0, :$n4 = 0, # more named parameters
%slurpy
) { ... }
foo('a', 'b', n4 => 4, n1 => 1, other => 99, n2 => 2);
%slurpy contains (other => 99), and
%qslurpy contains (n4 => 4, other => 99).
A hash query parameter can appear anywhere in the signature, including in
amongst positional parameters, but in that case it is an error unless it
is an even number of positional parameters before any first named
parameter:
sub foo($p1, ?%query, $p2, $p3, :$n1, :$n2) { ... } # ok
sub foo($p1, $p2, ?%query, $p3, :$n1, :$n2) { ... } # compile-time err
=head2 Restrictions on query parameters.
Apart from the scalar version $?foo, they cannot have default values:
??$foo = 0 makes no sense and slurpies (@foo and %foo) aren't allowed
default values anyway.
They cannot be a placeholder: i.e. these aren't legal: ??$, ?$, ?@, ?%.
The boolean form, ??$foo, cannot be aliased. The other types are ok.
They cannot be named parameters, e.g. ?:$foo.
They *can* have attributes; e.g.: ?$foo :ro.
Apart from the scalar version $?foo, they cannot have constraints (see
the "Type and Value Constraints and Coercions" thread for details about
constraints).
A query parameter used at the end of a signature is a compile-time error,
=head2 Some considerations and alternative proposals
Here are some of the other suggestions and ideas for getting back some of
the information associated with the now abandoned @_, and why I rejected
them.
Note that I'm not fond of anything which implicitly creates variables, e.g.
an :argcount subroutine attribute which magically declares 'my $argc'.
Ditto for implicitly created predicate variables.
The following ticket has a discussion on predicates (i.e. something that
indicates whether a parameter was passed an argument):
Subject: [perl #132444] parameter predicates in signatures
The three main approaches suggested in that ticket are:
* Follow the real variable with a predicate var preceded by a '?':
sub abc ( $foo = undef, ?$has_foo) { ... }
This is the basic approach I have taken, although I've generalised
it into the concept of query parameters. Also with my proposal, the
query parameter comes first.
* Include the predicate var as part of the parameter:
sub foo ($x, $y ? $has_y) { ... }
which wasn't well liked.
* Allow SVs to have an alternative 'undefined' state ('uninitialised',
say) so that a parameter variable's state can be differentiated
between undefined and never set. But what happens if such an
uninitialised variable is used as an argument to a second function?
Suddenly it's turtles all the way down.
Also, its not clear to me how this could be implemented.
* use an attribute:
sub foo($self, $x : passed($has_x) = undef) { ... }
but arguments to attributes are just single-quoted strings, so
tricking the toker/parser into declaring 'my $has_x' would be messy.
* get the equivalent of scalar(@_) from caller(). I hate this. It's
clunky, and using the argument count to determine whether a
parameter is passed is messy.
Thread Previous
|
Thread Next