http://qs1969.pair.com?node_id=170918

PetaMem has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks,

I'm humbly asking for explanation of basic perl knowledge. What puzzles me is that I'm having a code like that:

my @lexlist = @_; @lexlist = sort keys %lexicon if(!@lexlist);
which works very well. This is the beginning of a subroutine and all I want is to have in lexlist either a list of elements I gave as arguments to this routine or - if none were given - to create this list from some hash.

My problem is, that me thinks the following should be possible also:

my @lexlist = @_ or sort keys %lexicon;
unfortunatedly this doesn't work at all and I have no clue why. It seems, that @_ is always evaluated to a true value even if it is empty. Why this exceptional behaviour?

I'm sure I'm missing something very very basic...

Bye
 PetaMem

Replies are listed 'Best First'.
Re: @_ still mystifies me
by Ovid (Cardinal) on Jun 01, 2002 at 17:36 UTC

    A great way of figuring how Perl parses a particular piece of code is to use the B::Deparse module. That will attempt (usually successfully) to deparse your code and make its meaning more clear. To run it against a program, you would do something like this:

    perl -MO=Deparse someprog.pl

    The output will be Deparse's interpretation of what the code really does. Usually, the result is not too far off of what you have. In this case, I ran Deparse against you snippet and the result surprised me. Not in what it was saying to do, but in how it said it. In any event, it was much more clear. This should help you in the future -- I hope:

    $ perl -MO=Deparse -e 'my @lexlist = @_ or sort keys %lexicon' sort keys %lexicon unless my(@lexlist) = @_;

    So, as you can see, that parsed completely differently from your expectations. Hope this helps.

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

      I'm glad Ovid (++) mentioned the Deparse module, but be aware that different options can affect the output:

      # no options: $ perl -MO=Deparse -e 'my @lexlist = @_ or sort keys %lexicon' sort keys %lexicon unless my(@lexlist) = @_; #--- # using -x7 (level 7 expands code into equivelant logical constructs # using &&, ?:, and do{}) -- (giving us back nearly the original) $ perl -MO=Deparse,-x7 -e 'my @lexlist = @_ or sort keys %lexicon' my(@lexlist) = @_ or sort keys %lexicon; #--- # however, using -p (to fully parenthesize) can often help expose # such problems as well: $ perl -MO=Deparse,-p -e 'my @lexlist = @_ or sort keys %lexicon' ((my(@lexlist) = @_) or sort(keys(%lexicon)));

      It is sometimes useful to explore the output of Deparse with various options applied.

(jeffa) Re: @_ still mystifies me
by jeffa (Bishop) on Jun 01, 2002 at 15:37 UTC
    I think you should have to use the ternery operator here:
    my @lexlist = @_ ? @_ : sort keys %lexicon;
    Update - contrary to glancing, || as well as or will not work in this example. By using || or or with parentheses, sort keys %lexicon is evaluated in scalar context. Here is some code to demonstrate:
Re: @_ still mystifies me
by Zaxo (Archbishop) on Jun 01, 2002 at 15:38 UTC

    You're getting bit by both the low precedence of operator or and its scalar context.. I think you mean:

    my @lexlist = @_ ? @_ : sort keys %lexicon;
    I nearly got bit by context there myself :)

    After Compline,
    Zaxo

Re: @_ still mystifies me
by cLive ;-) (Prior) on Jun 01, 2002 at 15:33 UTC
    Presedence. "@_ is always evaluated to a true" - No, "@lexlist = @_" is.

    I think these braces are in the right place:

    my @lexlist = @_ or sort keys %lexicon; # read as (my @lexlist = @_) or (sort keys %lexicon);
    What you want is:
    my @lexlist = @_ || sort keys %lexicon; # read as my @lexlist = (@_ || sort keys %lexicon);
    Or use braces:
    my @lexlist = (@_ or sort keys %lexicon);
    Hopefully the braces make it a little more readable :) or, and & not have much lower presedence than || && and !

    In my experience, to avoid such confusion, I avoid the English versions...

    cLive ;-)

    Update: see below :) /me eats humble pie and revisits old scripts to see why this has never caused me a problem b4...

      This won't work because or forces scalar context on its operands.
      Update: Thanks, merlyn.
      ____________
      Makeshifts last the longest.
      You didn't test your code, and your answer doesn't work.
Re: @_ ... side step, use an arrayref
by George_Sherston (Vicar) on Jun 01, 2002 at 23:45 UTC
    This seems like reason nr 128 in the series for passing args to subs as references to data structures rather than the structures themselves. In this case an arrayref.

    Then you can use the high precedence (logical) Or, ||. Critically, this has higher precedence than =. So $dinner = $ham || $eggs; means dinner is ham or, if we've got no ham, eggs. As opposed to $dinner = $ham or get('takeaway'); which means dinner is ham, but if we fail to make dinner from ham, we're gonna send out.

    As other monks remark, || forces scalar context on the LH operand, so if you pass a three-element array, you'd get '3'. But with an arrayref you're already in scalar context so you don't care.

    The only disadvantage is, you don't use @_ explicitly. This is a disadvantage because that was what your question was about!
    use strict; use Data::Dumper; my %lexicon = ( foo => 'boink', bar => 'blink', baz => 'squirm', ); sub grop { my $lexlist = shift || [sort keys %lexicon]; print Dumper($lexlist); } grop(['gump','snoop','grock']); grop();
    Which produces
    $VAR1 = [ 'gump', 'snoop', 'grock' ]; $VAR1 = [ 'bar', 'baz', 'foo' ];


    § George Sherston
Re: @_ still mystifies me
by tadman (Prior) on Jun 02, 2002 at 22:49 UTC
    Disappointments:
    # Unfortunate scalar conversion my @foo = @_ || @bar; # Unfortunate error, not DWIM-compatible my @foo = @_; @foo ||= @bar;
    Although there are many good solutions posted here, how about a bad one?
    my @bar = qw[ dog cat frog ]; sub foo { my @foo = grep{length}(@_, (!@_ && @bar)); print join(',', @foo),"\n"; }
    Hey, it works, but it's quite brittle. If you even think about changing the order of the parameters, you're into SCALAR land.
Re: @_ still mystifies me
by Cody Pendant (Prior) on Jun 03, 2002 at 03:23 UTC
    This works for me:

    unless(@lexlist){@lexlist = sort keys %lexicon};

    I think what you're missing is that you can't use "or" in that way, not what happens to @_.

    UPDATE:

    Here's my test to prove that @_ is not always evaluated to true:

    &lex(); &lex(1,2,3); sub lex{ @lexlist = @_; if(@lexlist){ print "I got the lexlist, here it is: @lexlist \n" }else { print "where's the list?\n"; } }

    Which answers your question. --

    ($_='jjjuuusssttt annootthheer pppeeerrrlll haaaccckkeer')=~y/a-z//s;print;
Re: @_ still mystifies me
by rje (Deacon) on Jun 03, 2002 at 14:11 UTC
    It's too bad perl doesn't let you do that. It seems pretty DWIM to me -- and concise, too.
    Maybe perl6 will change that.

    Regards,
    print ((split '', 'just another perl hacker' )[-1,0,10]);