develooper Front page | perl.perl5.porters | Postings from September 2023

refaliasing list assignment in list context

Thread Next
Dave Mitchell
September 21, 2023 18:39
refaliasing list assignment in list context
Message ID:
TL;DR: refaliasing list assignment in list context is neither documented
nor tested, and the semantics are not obvious and need to be agreed and
possibly altered.


In normal list assignments, it's possible for the list assignment itself
to be in list context, either rvalue or lvalue. For example:

First, there's void context:

    ($a, @b) = ($x, $y, $z); # does the obvious

Then there's rvalue list context:

    @m = ( ($a, @b) = ($x, $y, $z) );

which is about equivalent to:

    ($a, @b) = ($x, $y, $z);
    @m = ($a, $b[0], $b[1]);

and finally, lvalue list context:

    ( ($a, @b) = ($x, $y, $z) ) = @m;

which is about equivalent to:

    ($a, @b) = ($x, $y, $z);
    ($a, $b[0], $b[1]) = @m;

All straightforward, documented and tested. Note that generally speaking,
the return value of list assign in list context is all the LHS elements,
with aggregates being flattened.

But then there's experimental reference aliasing, which aliases elements
rather than assigning them:

    use feature 'refaliasing';
    no warnings 'experimental';

    (\$a, \(@b)) = (\$x, \$y, \$z);

This aliases $a to $x, $b[0] to $y and $b[1] to $z. This is documented
and tested.

But there is nothing in the test suite or elsewhere in core which
exercises these:

    @m = ( (\$a, \(@b)) = (\$x, \$y, \$z) );
    ( (\$a, \(@b)) = (\$x, \$y, \$z) ) = @m;
    foo( (\$a, \(@b)) = (\$x, \$y, \$z) );

and as far as I can tell, its behaviour isn't documented. So the question
for this thread is, what should be the expected behaviour?

For example in:

    ( (\$a, \(@b)) = (\$x, \$y, \$z) ) = @m;

should the outer list assign be equivalent to:

    $a =    $m[0];
    $b[0] = $m[1];
    $b[1] = $m[2];
    \$a =    $m[0]; # croaks if $m[i] isn't a ref
    \$b[0] = $m[1];
    \$b[1] = $m[2];
    \$a =    \$m[0];
    \$b[0] = \$m[1];
    \$b[1] = \$m[2];

Or something else?

My gut feeling is that it should probably be (1). Part of me thinks that
the parser should be clever enough to mark the outer assign as also doing
refalising, so it expects its LHS and RHS to be refs, so (2). But this is
problematic when the lvalue context is provided by a sub call:

    foo( (\$a, \(@b)) = (\$x, \$y, \$z) );

in that case I would expect the args passed to foo() to be $a, $b[0],
$b[1] rather than \$a etc. So (1) it is.

However, as currently implemented, it does none of the above. $a and $x
remain as two aliases to to the same unchanged SV, and similarly for $b[0]
and $y, $b[1] and $z. The values in @m don't end getting assigned to or
aliased to anything.

Technically what is happening is that the inner list assignment in list
context returns a list of temporary refs to the aliased SVs. In rvalue
contexts, these temp RVs can be assigned, e.g.:

    @m = ( (\$a, \(@b)) = ($rx, $ry, $rz) );
    # @m is now a list of refs.

    ${$m[0]} = 1; # equivalent to $a = 1;

In lvalue list context, these temporary RVs get the value assigned to
them, rather than to the thing they refer to. Which seems pointless.

So my vague proposal is that we should change the behaviour of refaliasing
in list lvalue/rvalue context so that the returned list is a list of
the aliased SVs, not a list of temporary refs to aliased SVs.

Alternatively we could just croak in list context (either at compile time,
or in runtime for something like sub f { ...; \(@a) = .... }. Or we could
just decree that aassign returns the empty list in list context, or ... ?

But I'm mostly confused by the whole thing at the moment, and I've being
staring at the code in Perl_pp_aassign for days now.

I don't want to achieve immortality through my work...
I want to achieve it through not dying.
    -- Woody Allen

Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About