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

Monks, I was attempting to answer the Cartalk Puzzler when I came across something else that puzzled me.

I expect the following code to behave the same whether using "normal" if or "ternary" ? : ; style if. But, I get different results. Can someone explain this?

use strict; use warnings; use Data::Dumper; my %lights; $lights{$_} = 0 for (1..4); foreach my $light ( sort { $a <=> $b } (keys %lights) ){ foreach my $divisor ( 2..4 ) { if ( $light % $divisor == 0 ) { # if it divides evenly, pu +ll the cord if ( $lights{$light} ) { $lights{$light} = 0; } else { $lights{$light} = 1; } } } } report({%lights}); # outputs 1 4 $lights{$_} = 0 for (1..4); foreach my $light ( sort { $a <=> $b } (keys %lights) ){ foreach my $divisor ( 2..4 ) { if ( $light % $divisor == 0 ) { # if it divides evenly, pu +ll the cord $lights{$light} ? $lights{$light} = 0 : $lights{$light +} = 1; # turn off if it's on, and vice versa. } } } report({%lights}); #outputs 1 sub report { my $lights = shift; print "off lights: "; foreach (sort { $a <=> $b }( keys %{$lights} ) ) { print "$_ " unless $lights->{$_}; } print "\n"; }

Replies are listed 'Best First'.
Re: Ternary if versus normal if question
by blokhead (Monsignor) on Nov 17, 2005 at 13:24 UTC
    Run the code with -MO=Deparse,-p, and the line with the ternary operator becomes:
    (($lights{$light} ? ($lights{$light} = 0) : $lights{$light}) = 1);
    You can see that the 2nd assignment operator has lower precedence than the ternary operator. So this says,
    If $lights{$light} is true, then assign 1 to the result of "$lights{$light}=0", otherwise assign 1 to the result of $lights{$light}.
    ... which is not what you want. A better way to use the ternary here is:
    $lights{$light} = $lights{$light} ? 0 : 1;
    Or even better still is probably:
    $lights{$light} = ! $lights{$light};
    which toggles $lights{$light} between true & false.

    blokhead

      Also, the docs for B::Deparse around the -xLEVEL option pretty much implies that ternaries and ifs are equivalent:

      If *LEVEL* is at least 7, "if" statements will be translated into equivalent expressions using "&&", "?:" and "do {}"; for instance print 'hi' if $nice; if ($nice) { print 'hi'; } if ($nice) { print 'hi'; } else { print 'bye'; } turns into $nice and print 'hi'; $nice and do { print 'hi' }; $nice ? do { print 'hi' } : do { print 'bye' };

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Ternary if versus normal if question
by wfsp (Abbot) on Nov 17, 2005 at 13:23 UTC

    If you change:

    $lights{$light} ? $lights{$light} = 0 : $lights{$light} = 1;
    to:
    $lights{$light} = $lights{$light} ? 0 : 1;
    it works ok.

    From perlop:

    If the argument before the ? is true, the argument before the : is returned, otherwise the argument after the : is returned.

    update:

    Added quote from the docs

Re: Ternary if versus normal if question
by blazar (Canon) on Nov 17, 2005 at 14:03 UTC
    I expect the following code to behave the same whether using "normal" if or "ternary" ? : ; style if.

    You seem to be confused. C<?:> is an operator, whereas if is a flow control syntactical keyword. While you can use the former instead of the latter:

    (.5<rand) ? print "Wow\n" : die "horribly";
    you really shouldn't. I may get heavily downvoted for saying that you "can", so I'll stress once more that you really really shouldn't. Except in golf and obfu, that is.

    The point is that you should use C<?:> if you're interested in its return value. And of course you can't use if instead of it, because the latter does not return anything. You will rapidly learn in which situations it is worth to use one and in which ones it is worth to use the other...

      Thanks very much to you and all those who clarified the ternary if to me. However, what is meant by the C in C<?:>? Is that shorthand for some concept?
        It's the pod format of Perl documentation. Apart that, it is also used in ASCII contexts to visually mark code, but I generally avoid it here since whe have <code> tags anyway. In this situation, however, it seemed to me to be too be too short and confusing, so I added that pseudo-markup.

      Why shouldn't you? Perl idiom says that:

      (.5<rand) or die "horribly";

      is acceptable. In fact even encouraged. Can you explain to OP and others why your very compact and easily read example is so much worse than:

      if (.5<rand) { print "Wow\n"; } else { die "horribly"; }

      Granted, were the string in either case much bigger, or were more lines of code required than shown in either case, if starts to look much better. Even better however would be the or die idiom.

      However, this doesn't really apply to OP's context where $var = <cond> ? <case 1> : <case 2>; is ok and $var = ! $var; is much better.


      DWIM is Perl's answer to Gödel
        Why shouldn't you? Perl idiom says that:
        (.5<rand) or die "horribly";
        is acceptable. In fact even encouraged. Can you explain to OP and others why your very compact and easily read example is so much worse than:

        It is worse in that short circuiting or used in contexts like the one you're referring to makes for very clear syntax imitating its use in natural languages. Indeed the practical rule of a thumb is to use low-precedence logical operators for flow control and high-precedence ones to operate on values. Of course there are reasonable situations in which it is reasonable to violate this "rule", but in most cases it does apply.

        if (.5<rand) { print "Wow\n"; } else { die "horribly"; }

        To be fair I happen rarely enough to (have to) use a full if ... then ... else construct like the above. In this case, for example

        die "horribly" if .5 < rand; print "Wow\n";
        would suffice. Of course this is specific of the particular example under examination, but I make a frequent use of blocks (sometimes also for loops having the sole purpose of aliasing to $_) and of last, next, and more rarely redo and they make for quite as compact but still perfectly readable syntax.

Re: Ternary if versus normal if question
by Moron (Curate) on Nov 17, 2005 at 14:29 UTC
    And yet, why not $lights[$light] = !$lights[$light];

    -M

    Free your mind

      Or, more succinctly, $lights[$light] ^= 1;.

        Beware of strings vs numbers, though. The bitwise ops in Perl are hazardous if used imprudently.

        Makeshifts last the longest.

        I looked for a solution with ^=, but it doesn't work unless it can be made into a unary operator at which point I gave up looking down that route. Your suggestion only turns the light on when off but does not turn it off when on.

        -M

        Free your mind