perl-diddler has asked for the wisdom of the Perl Monks concerning the following question:

I have some code that handles 'variables' in a class's "hash". Various reasons for it, but main that I remember (it's been around for a few years), was to prevent me from accidently putting in 'garbage' in the {} when I accessed a class var. In line with that, the Data::Vars class does various types of complaining, some of which can be silenced, but will complain about trying to access a var not in the class (or initialize a var not in the class), can complain if you already have an accessor for the 'var', can set it up to perform "default initializations", for the vars that get set on any new object, if a ref, can make sure the "type" isn't changed at runtime, by accident (i.e. a warning). among a few other things. They get used a fair amount in much of the code I write. An example from code running against 5.16:
#!/usr/bin/perl our @fields; use mem(@fields=qw(one two three)); use Data::Vars \@fields, {one=>1, two=>2, three=>3}; use P; my $self=main->new(); P "one=%s, two=%s, three=%s", $self->one, $self->two, $self->three;
running it produces no big surprises:
> /tmp/t.pl one=1, two=2, three=3
Where my surprise has come is trying to run in under 5.14 on cygwin. I get an immediate error:
> /tmp/t.pl Can't modify logical and (&&) in lvalue subroutine return at /Users/la +w.Bliss/bin/lib/Data/Vars.pm line 108, near "}" BEGIN not safe after errors--compilation aborted at /Users/law.Bliss/b +in/lib/Data/Vars.pm line 111. Compilation failed in require at /tmp/t.pl line 4. BEGIN failed--compilation aborted at /tmp/t.pl line 4.
This has me stumped, since the line it is pointing to just has the "}" on the line (it closes a routine I call the 'main workhorse' of the class). Additionally, in that routine, there ARE no uses of '&&'. While the Vars.pm module has other code in it, this work horse routine is relatively short at 44 lines:
sub _Var ($$$;$) :lvalue { # Wrkhorse code for manu +fctrd Vars my ($p, $vn, $wa) = @{(shift)}; # vn=varname my $c = ref $p || $p; my $rfv = ref $p->{$vn}; # rfv=ref(type) of var my $addrof; if ($wa && $wa eq 'α') { $wa=undef, $addrof=1 } my $arg = $_[0]; if (@_ && !$addrof) { # arg for "setter" my $rfa = ref $arg; # ref of the arg unless ( defined($p->{$vn}) && defined $arg ){ # if both not de +fined $p->{$vn} = $arg } elsif ($rfv eq ARRAY) { ## if type(var)==ARRAY, +1 param my ($index, $ap) = (shift, $p->{$vn}); if ($p->{':pusharray'} or ( !looks_like_number($index) || not( (defined $wa) || @_) )) { push @{$p->{$vn}}, ($index); ## convert to "push" return $index } ## return pushed val +ue else { $p->{$vn}[$index] = $_[0] if @_; return $p->{$vn}[$index] if defined $wa } } elsif ($rfv eq HASH) { my $subvar = shift; ## 1 var w/hash is is +a key $p->{$vn}{$subvar} = $_[0] if @_; ## another? =>assign +value return $p->{$vn}{$subvar} if defined $wa } else { if ((length $rfv) && $rfv ne $rfa ) { ## incompat assignment warn P "Warning: var type redefined from \"%s\" to \"%s\"", $rfv, $rfa } $p->{$vn} = $_[0]; ## assignment is default return $p->{$vn} if defined $wa } } # how to return results? + (below) if ($rfv eq ARRAY ) { if (defined($wa)) { # arrays special $wa? @{$p->{$vn}} : $addrof ? \$p->{$vn} : $p->{$vn} } } elsif ($rfv eq HASH ) { $p->{$vn} } elsif ($addrof) { return $p->{$vn} } else { return $p->{$vn} } } ## endsub (#108) # "address(ref)-of" function ( sub _addr_of_($) { (#111 -- basically next line of code)
The line with the 'endsub' is line #108 -- the line with the message that says:
can't modify logical and (&&) in lvalue subroutine return.
in it.

Not very helpful. Trying to run it under the debugger, I get a bit more information:

> perl -d bin/dedup Loading DB routines from perl5db.pl version 1.33 Editor support available. Enter h or `h h' for help, or `man perldebug' for more help. Can't modify logical and (&&) in lvalue subroutine return at /Users/la +w.Bliss/bin/lib/Data/Vars.pm line 108, near "}" BEGIN not safe after errors--compilation aborted at /Users/law.Bliss/b +in/lib/Data/Vars.pm line 144. at /Users/law.Bliss/bin/lib/Data/Vars.pm line 144. require Data/Vars.pm called at bin/dedup line 21 main::BEGIN() called at /Users/law.Bliss/bin/lib/Data/Vars.pm +line 144 eval {...} called at /Users/law.Bliss/bin/lib/Data/Vars.pm lin +e 144 Compilation failed in require at bin/dedup line 21. at bin/dedup line 21. main::BEGIN() called at bin/dedup line 21 eval {...} called at bin/dedup line 21 BEGIN failed--compilation aborted at bin/dedup line 21. at bin/dedup line 21. Debugged program terminated. Use q to quit or R to restart, use o inhibit_exit to avoid stopping after program termination, h q, h R or h o to get additional info. DB<1>
I.e. The problem might be closer to 144? What is there?...
BEGIN { #140 if (require "Dbg.pm") { # import if there, else not; no error if + not using TPe Dbg->import; } #143 } #144
...Another closing brace and no '&&' in sight. That is called from line 21 in dedup:
use Data::Vars \@fields, {mmap=>1, aio_respQ=>{}, examine_paths=>[], S +YS_IO=>1, use_child_ra=>1};
...This would seem to be a normal line to define & init vars in that module. Still no sign of logical 'AND'.

....Hmmm... I'm stumped. If anyone knows the secret code behind the above encrypted error message... and could explain what I should be looking for to fix, (or what I should fix), would appreciate it. I' be happy to post the rest of Data::Vars (~400 lines total), if people think that would be useful.

Anyone seen this type of error before...? The closest I can think of is when I have tried something like:

$a==NULL && $a=xxx; (needs parens or 'and' instead of '&&').
But don't see anything resembling that in the above or the rest of the code.

Ideas?

Replies are listed 'Best First'.
Re: weird error message in middle-aged-perl(5.14)
by Athanasius (Archbishop) on May 09, 2014 at 02:42 UTC

    From perl5160delta#Lvalue-subroutines (second bullet point):

    Lvalue subroutines used to enforce lvalue syntax (i.e., whatever can go on the left-hand side of =) for the last statement and the arguments to return. Since lvalue subroutines are not always called in lvalue context, this restriction has been lifted.

    The “last statement” immediately before line 108 is:

    if ($rfv eq ARRAY ) { if (defined($wa)) { # arrays special $wa? @{$p->{$vn}} : $addrof ? \$p->{$vn} : $p->{$vn} } } elsif ($rfv eq HASH ) { $p->{$vn} } elsif ($addrof) { return $p->{$vn} } else { return $p->{$vn} }

    In the case where $rfv does equal ARRAY but $wa is not defined, this “last statement” does nothing, so the sub will return undef, which is not a valid lvalue.

    That’s just my guess as to what is going on. I don’t know where the && in the error message is coming from, but I note that a similar message was reported in http://www.perladvent.org/2005/21/. I can’t experiment as I don’t have version 5.14 installed.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Good answer! Allow me to supply the missing puzzle piece.

      "That’s just my guess as to what is going on. I don’t know where the && in the error message is coming from"

      The following two statements are logically equivalent:

      # logical and foo() && bar(); # postfix if bar() if foo();

      The Perl parser in fact compiles them to the same optree. You can see this by running the following commands:

      $ perl -MO=Concise -E'foo() && bar();' $ perl -MO=Concise -E'bar() if foo();'

      After Perl has finished parsing the statement and has built its optree, it "forgets" the code which it originally saw. When it needs to generate an error message, it has to take a guess at whether the error message should talk about "if", "&&", or indeed "and" — in this case, it's gotten it wrong.

      Linda doesn't have any "&&" operators in her code, but she does use the postfix "if" in some places, and it's this which is being complained about by Perl.

      As an aside, for a much easier way of writing lvalue subs, take a look at LV.

      use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

      Running the following program under ActiveState 5.8.9 and Strawberries 5.10/12/14 (all Win32) gives me identical results:

      c:\@Work\Perl>perl -wMstrict -le "my $x = 0; my $y; ;; sub S :lvalue { if ($x) { $y; } } ;; S() = 'foo'; ;; print qq{x '$x' y '$y'}; " Can't modify logical and (&&) in lvalue subroutine return at -e line 1 +, near "} }" Execution of -e aborted due to compilation errors.

      Given the experimental nature of  :lvalue trumpeted in the Lvalue subroutines section of perlsub, I would be very reluctant to use it with a complex subroutine. If the Perl compiler can't even figure out the simple example code above, what hope for the vipers' nest of conditionals that is the  _Var() workhorse?

      (Somewhere in the back of my mind is something about the compiler sometimes optimizing a statement like
          if ($x) { do_this(); };
      to
          $x && do_this();
      so this may be part of the problem — but don't quote me on this!

      Update: Actually, it's  $y if $x; that is so optimized/compiled, using and not &&. Nevermind...)

        Given the experimental nature of :lvalue trumpeted in the Lvalue subroutines section of perlsub, I would be very reluctant to use it with a complex subroutine. If the Perl compiler can't even figure out the simple example code above, what hope for the vipers' nest of conditionals that is the _Var() workhorse?
        Point taken -- but to it's credit, _Var was around for a few years BEFORE I added lvalue... i.e. I'd use it:
        ...(skipping prologue) my $p=main->SUPER::new({scalar=>1, arr=>[1,2,3,4], hsh=>{one=>1, two=>2, three=>3}}); #w/o lvalue: $p->scalar($p->scalar+1); $p->arr(1,22); $p->arr(3,$p->arr(3)+$p->arr(1)); $p->hsh("two",22); $p->hsh("total", $p->hsh("one")+$p->hsh("two")); P "arr=%s", [$p->arr]; P "hsh=%s", $p->hsh; Vs. w/lvalue: $p=$p->SUPER::new({arr=>[1,2,3,4], hsh=>{one=>1, two=>2, three=>3}}); ++$p->value; #or ($p->value++;) $p->arr(1) = 22; $p->arr(3) += $p->arr(1); $p->hsh("two") = 22; $p->hsh("total") = $p->hsh("one")+$p->hsh("two"); P "arr=%s", [$p->arr]; P "hsh=%s", $p->hsh; #both give same results: arr=[1, 22, 3, 26] hsh={one=>1, three=>3, total=>23, two=>22} arr=[1, 22, 3, 26] hsh={one=>1, three=>3, total=>23, two=>22}
        For data that doesn't need runtime checking -- just dynamic allocation in a structure, the lvalue'd versions work great and are considerably less visual 'mess' to use, BUT, as you mention, experimental means semi-worthless for production code. As it is, I tend toward using the non-lvalue form in about 2/3rd of new *assignments*. But when you do a read-modify-write, the lvalue form is awfully tempting.

        Gonna go poke at the return vals as suggested by Athanasius and see if that clears up the error...

      Well.. not exactly it, but put me on track to finding it.

      It didn't like the "\$p->{$vn}".

      Once I fixed that, the error kicked up to the calling routine, which "was":

      sub _access_maker { #{{{ my $pkg = shift; #var in $_ { my $proc = '# line ' . __LINE__ . ' "' . __FILE__ . "\"\n" . ' { use warnings;use strict; package '.$pkg.'; sub '.$_.' (;$) :lvalue { unshift @_, [shift, Data::Vars::varname((caller 0)[3]), want +array]; goto &Data::Vars::_Var}; 1}'; eval $proc; $@ and die "Fatal error in $pkg\::Vars\::_access_maker?: $@\n"; } } ## end sub _access_maker }}}
      It didn't like the 'goto'. So... just a bit of uglification:
      sub _access_maker { #{{{ my $pkg = shift; #var in $_ { my $proc = '# line ' . __LINE__ . ' "' . __FILE__ . "\"\n" . ' { use warnings;use strict; package '.$pkg.'; sub '.$_.' (;$) :lvalue { ' . ($] >= 5.016 ? ' unshift @_, [shift, Data::Vars::varname((caller 0)[3]), want +array]; goto &Data::Vars::_Var ' : ' &Data::Vars::_Var( [shift, Data::Vars::varname((caller 0)[3]), wantarray], +@_); ') .'}; 1}'; eval $proc; $@ and die "Fatal error in $pkg\::Vars\::_access_maker?: $@\n"; } } ## end sub _access_maker }}}
      Ug...Talk about chaotic changes!.... Gonna have to do a bit more testing with this mess of changes...ARG!!!!!!

      Thanks!...(I think...*ouch*...)

Re: weird error message in middle-aged-perl(5.14)
by amon (Scribe) on May 09, 2014 at 08:17 UTC

    Lvalue subroutines should have a single point of return – and not use the return keyword. So something like

    my @x; sub foo :lvalue { my ($cond, $i) = @_; if ($cond) { return $x[$i]; } else { return $x[0]; } }

    should instead be written as

    my @x; sub foo :lvalue { my ($cond, $i) = @_; my $lvalue; if ($cond) { $lvalue = \$x[$i]; } else { $lvalue = \$x[0]; } $$lvalue; }

    which is a general solution, or

    my @x; sub foo :lvalue { my ($cond, $i) = @_; my $lvalue; ($cond) ? $x[$i] : $x[0]; }

    Edit: further notes I see you are using the mem module, which is basically useless. If you need to perform an assignment at compile time, just use a BEGIN block:

    our @fields; BEGIN { @fields=qw(one two three) }

    While this is a little more verbose, you now don't need any non-standard modules. A similar rationale applies to the P package. It attempts to be very DWIM-y, but I find it's design distasteful. Here, explicitly using the builtin printf rather than the P function would have been clearer.

    When posting minimal code snippets for others to reproduce a problem, then it's customary to remove such irrelevant dependencies – I don't shove my similar Util::Underscore in everyone's face.

      You misunderstand. If you require me to rewrite the code I'm am having a problem with in order for you to be able to help, then realize, that I don't want your help. It's too costly.

      Just like I used "..." to skip sections not important, I use shorthand in my examples that allow me to focus on the problems, not superfluous code. "P" is like "..." it's a generic print operator that embodies the spirit of perl's DWIM philosophy -- something you find distasteful. Why use perl if you find it's core philosophy distasteful.

      Also, I would wager it's not P's design that is distasteful, but it's feature set. Since, if I asked you how you would design it, you'd say you wouldn't -- meaning it's not a matter of design, but a matter of it's functionality.

      How can functionality that embodies perl print operators be distasteful?