develooper Front page | perl.perl5.porters | Postings from January 2001

Re: `Final' lvsub patch: arrays and hashes

Thread Previous | Thread Next
From:
Stephen McCamant
Date:
January 7, 2001 14:26
Subject:
Re: `Final' lvsub patch: arrays and hashes
Message ID:
14936.54769.247313.520662@soda.csua.berkeley.edu
>>>>> "SC" == Simon Cozens <simon@cozens.net> writes:
SC>     sub foo :lvalue {shift}  foo($a) = 7 

SC> On Sun, Jan 07, 2001 at 12:30:06PM -0500, Ronald J Kimball wrote:
RJK> "it is possible to return a modifiable value from a subroutine"
RJK> does not at all mean the same thing as "all values returned from
RJK> a subroutine are modifiable".

SC> Fair enough. Try this:
SC>     I am returning a value from a subroutine.
SC>     The value I am returning is the value of the first parameter.
SC>     The value of first parameter of the subroutine is modifiable.
SC>     The value that I am returning is modifiable.

SC> Where's the fallacy?

I don't think your expectation of being able to assign to foo() is
based on fallacious reasoning; it's based on a particular
self-consistent idea of how modifiability should work, which as a
language design issue I think would be bad to enshrine in the
semantics of lvalue subroutines, since it limits our ability to detect 
mistakes and unnecessarily exposes implementation details.

The semantics of lvalue subroutines as they stand in the status quo
you're more or less defending isn't self-contradictory, but it is a
little weird, and I don't think you can give a principled explanation
of it without describing the way it's implemented in terms of SVs.
For the purposes of lvalue subroutines, an SV is `modifiable' if it
isn't read-only (in these sense of SVf_READONLY), it isn't a mortal
temporary (in the sense SVs_TEMP), and it isn't a pad target temporary 
(in the sense of SVs_PADTMP). This is a runtime notion, and it's
checked at runtime in pp_leavesublv() -- if any of the SVs being
returned are in any of the three excluded categories, perl croaks. For 
the sake of comparison, consider the different notion of modifiability 
that appears in the semantics of assigning through a reference; for
that construct, an SV is modifiable whenever is isn't read-only. Thus
you can say any of the following:

${\(2+$x)} = 7;                    # pad targ
${\scalar(getpwuid($<))} = "root"; # mortal temp
${\$x[0]} = 7;
@x=1; ${\shift(@x)} = 7;           # mortal temp!

but not

${\5} = 7;
${\(2+3)} = 7;            # constant folded to read-only 5
@x=(); ${\shift(@x)} = 7; # non-existant array elt. is const undef

For the case of references, I think this sort of runtime determination
is the best (only) option; contrary to the examples above, in most
cases there isn't a clear syntactic relationship between the creation
and use of a reference, so static checks wouldn't be practical, and C
programmers are used to using pointers to get around the compiler's
cleverness and work on a lower level (c.f. *(NV**)xnv = (NV *)xnv).

The situation with lvalue subroutines, on the other hand, is very
different. When a reference is created, we have in general no way of
knowing whether it will be assigned through (used as an lvalue,
essentially), or whether it will only be read. With lvalue
subroutines, the programmer has told us explicitly that a return value 
could be modified, so I think it behooves us to take advantage of this 
information and warn the programmer at compile time if they tried to
do something silly like return a constant or the result of an addition 
as an lvalue return value. It seems silly to allow a programmer to
write an lvalue subroutine that could never be successfully called in
an lvalue context. The controversial part of course is that there are
some constructs that aren't syntactically allowed to be assigned to,
but do produce SVs that aren't read-only or temporary, and so could
successfully be modified under only the runtime checks. Two examples
are the result of popping or shifting @_ (but not other arrays), or
the result of preincrement or predecrement of a value (but not
postincrement or postdecrement). As far as I can see, the only way to
explain why some things work and some things don't is essentially to
explain the way they're implemented; for instance, how @_ is different 
from a normal array. I think it's a bad idea to make things work this
way when it can be avoided -- it makes the workings of perl seem
unnecessarily mysterious, and limits future optimizations or other
improvements.

I suppose a possible compromise might be to have a compile-time
warning about returning non-lvalues from an lvalue subroutine, though
that would make doing so quasi-illegitimate in a weird way (it might
be necessary if a lot of users are already making use of these lvalue
returns; but I have no idea what response the 5.6 version of this
feature got). Also, even with the compile time check, you could use
the ${\()} trick above to return anything you wanted, if you really
wanted to.

Hope this clarifies things,

 -- Stephen McC

Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About