Front page | perl.perl5.porters |
Postings from November 2019
Scope and Ordering
From:
Dave Mitchell
Date:
November 28, 2019 17:06
Subject:
Scope and Ordering
Message ID:
20191128170521.GG3573@iabyn.com
We need to determine and document how the various parts of a signature
behave as regards to lexical scope, visibility, tainting and ordering of
things like default expressions and constraints.
=head2 Scope
I propose for lexical scoping that:
sub f($a, $b, $c, ... ) { BODY; }
Is logically equivalent to:
sub f {
my $a = ....;
my $b = ....;
my $c = ....;
....;
BODY;
}
In particular, each parameter element is independent taint-wise, and each
parameter variable has been fully introduced and is visible to default
expressions and the like in further parameters to the right of it.
For example, the first default expression in
sub foo($x = $x + 1, $y = $x + 2)
sees any outer or global $x rather than the parameter (similar to
C<my $x = $x + 1>), while the second expression sees the parameter $x.
'my' declarations in default expressions are visible to further parameter
elements and the main body of the program, e.g.
sub f($a = 1 + (my $x = 1), $b = $x, ...) { ... $x }
Formally, lexical parameter variables are introduced at the end of the
parameter declaration, and in particular are not visible to 'where' and
'as' clauses for the current parameter.
'local' declarations have similar scope.
(This is all already the current behaviour.)
However, there will be an implicit scope around the collection of
where/as/isa/is traits (see the "Type and Value Constraints and Coercions"
thread).
=head2 Ordering of evaluation of terms
There is much external visibility, both from explicit execution of things
like default expressions and constraints, and implicitly from things like
FETCH(), overloaded stringify, and attribute handlers. The question is how
are these ordered, and what do we guarantee?
I propose that a few aspects of ordering are well defined; everything
else is left undefined, to allow us to change things in different releases
for the purposes of optimisation etc.
Within a single parameter element, we guarantee this order:
1) attributes->import() is called as appropriate for any :attribute;
2) the default expression (if present and needed) is run;
3) the parameter variable is bound to its argument or default value;
4) the constraint expression (if any) is run.
Between parameter elements, we guarantee that parameters are processed in
left-to-right order. This means that that when calling any explicit code
for parameter N+1 (such as a constraint or default expression), all such
code for parameters 1..N will already have been called, and that
parameters 1..N will have already been bound to their arguments.
This applies to named parameters too, regardless of the ordering of the
name/value pairs in the argument list.
Anything else is undefined and subject to change. In particular:
* There are no guarantees exactly when error checking is performed and
thus when a croak() might happen; for example, an odd number of
arguments to a hash slurpy might be detected at the start of signature
processing, or only at the end of assigning to the hash.
* There are no guarantees of which order or when arguments are processed:
this may become visible for example as FETCH() calls for arguments,
string overloading for arguments treated as parameter names, dereference
overload for \@a-type aliasing, or uninitialized-value warnings.
Note that this doesn't apply to argument expressions; for example, in
f($x, g(), $y), the function g() will definitely have been called before
f's signature processing is started. However, if $x, $y and the return
value of g() are all overloaded, then there is no guarantee which of the
three overload method calls will be performed first.
This lack of ordering is especially important for handling named
parameters sanely and efficiently.
Perl will be free to re-order things internally, as long as it has no
user-visible side-effects that violate the promises given above.
=head2 Flow control
To maintain sanity and ordering, 'goto LABEL' I<into> a block containing a
default expression or constraint (or any other such code we might add to
parameter elements) should be explicitly disallowed and should croak.
It's currently deprecated.
That will stop abominations such as
sub f ($a = do { goto FOO }, $b = ..., $c = do { FOO: ...; }) { ... }
(what happens to $b here?)
It's okay to exit a sub via flow control within such a block e.g.:
sub f ($a = do { next SKIP if ... }, $b = do { return if ...; }) { ... }
Ditto last, redo. It's also okay to die, exit and _exit.
While a real fork should be okay, an ithreads pseudo-fork might have
difficulties, as would creating a new ithread. The documentation should
note this.
Any goto *out* of such a block and into the main body of the sub
should croak (or at least be undefined behaviour if not detectable):
sub f ($a = do { goto FOO }, $b = ...) {
...;
FOO:
...;
}
This is because it skips over the initialisation of further elements,
possibly ignoring constraints etc.
It's okay to goto out of a sub altogether.