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

Brethren,

while trying to "do it more than one way", I've stumbled across a weird behaviour of the ternary operator. I had this code which keeps only the recent input in history of Term::Readline:

if ($optimize_history) { # remove duplicities from history my @history = $term->GetHistory($file); my %history = map {$_ => 1} @history; my @new_history = (); for (reverse @history) { # in reverse order if ($history{"$_"}) { # if not remembered ye +t unshift @new_history, $_; # remember history l +ine delete $history{"$_"}; # and remove it } } $term->SetHistory(@new_history); # set history to new l +ist $term->WriteHistory($file); # save history } else { # only add new line to the history file $term->WriteHistory($file); }
after some refactoring (I especially didn't like the usage of so much variables/structures) I came up with something like that:
if ($optimize_history) { my @history = $term->GetHistory($file); my %history; # eliminate duplicates in history and keep only the most recent for(my $i=scalar(@history)-1;$i>=0;$i--) { $_ = $history[$i]; !$history{$_} ? $history{$_}=1 : splice(@history,$i--,1); } $term->SetHistory(@history); # set history to new list } $term->WriteHistory($file); # save history
to my big surprise, when I tried to refactor the ternary operator into
$history{$_} ? splice(@history,$i--,1) : $history{$_}=1;
I get an error message:
Can't modify splice in scalar assignment at ./history.pl line 35, near + "1;" Execution of ./history.pl aborted due to compilation errors.
Ehm...Uh? Why is that? Have I - again - been caught by some precedence issue?

Bye
 PetaMem

Replies are listed 'Best First'.
Re: Ternary Operator: Condition-Negation
by dws (Chancellor) on Aug 28, 2002 at 08:17 UTC
    Have I - again - been caught by some precedence issue?

    Yup. The ternary conditional operator (?:) has a higher precendence than assignment (=).   A ? B : C = 1 is treated as  (A ? B : C) = 1 In your case, C is a splice, which you can't modify. Try   split(@history,$i--,1) = 1 and you'll get the same error.

Re: Ternary Operator: Condition-Negation
by theorbtwo (Prior) on Aug 28, 2002 at 09:15 UTC

    You have indeed, as dws explained so well. A nice tool for situations like these, where you suspect perl might parse your code differently then you want it to, is B::Deparse, which shows you exactly what perl thinks your code is saying. In this case, using it looks somthing like this:

    C:\WINDOWS\Desktop>perl -c -MO=Deparse,-p -e "$history{$_} ? splice(@h +istory,$i--,1) : $history{$_}=1;" Can't modify splice in scalar assignment at -e line 1, near "1;" -e had compilation errors. (($history{$_} ? splice(@history, ($i--), 1) : $history{$_}) = 1);

    In this case, I want perl to try to compile without running (-c), using the O module (the compiler-front end; for technical reasons (read: reasons I don't recall) Deparse is considered a compiler), passing it the Deparse (tells it to use B::Deparse, and pass it the rest as parameters), and -p (tells deparse to give me lots of parenthesies). For more info on using B::Deparse, follow any of those links; there's also options to not munge various things to give you a look that's closer to perl's, but harder to read.

    Deparse is very useful for deobfuscating obfuscations, both intentional and otherwise. Oh, and it's core in 5.6 and later, IIRC. (For some reason, the CPAN results disagree with me, and say 5.8, which can't be right, since I have it and am on 5.6. Strange...)


    Confession: It does an Immortal Body good.

Re: Ternary Operator: Condition-Negation
by tadman (Prior) on Aug 28, 2002 at 09:51 UTC
    Ahh, refactoring. It reminds me of the days of Transport Tycoon when you're trying to find the right track combination that's both elegant and efficient. In this case, I think a whole lot of fuss can be trimmed right out, which in track terms means less money and faster transport. In programmatic terms, simpler can, to a point, mean fewer bugs, since the code is much more concise.

    What about this idiomatic solution?
    !$history{$_}++ && splice(@history, $i--, 1);
    Or even better, ditch that awful for-loop:
    my %seen; @history = reverse grep { !$seen{$_}++ } reverse @history;
Re: Ternary Operator: Condition-Negation
by BrowserUk (Patriarch) on Aug 28, 2002 at 13:39 UTC

    When I first saw this post I was struck by the non-perlishness of the following loop...

    # eliminate duplicates in history and keep only the most recent for(my $i=scalar(@history)-1;$i>=0;$i--) { $_ = $history[$i]; !$history{$_} ? $history{$_}=1 : splice(@history,$i--,1); }

    ...and thought I'd have a crack at "improving it".

    What I arrived at after going all round the houses was:

    { my (%history, $histcount); # Should I initialise $histcount = 0 or + is that "Cargo cult"? sub addHist { $history{ $_[0] } = $histcount++; } sub getHist { return = grep{ $history{ $_ } == ($histcount - $_[0]); + } keys %history; } } ... # get $newvalue from somewhere addHist( $newvalue ); ... # deside we want the $n'th previous value my $last_nth = getHist( $n ); ...

    If anyone is interested in seeing the route by which I arrived at this, or fancies answering a couple of question arising...

      Simplify?
      for ($#history .. 0) { my $temp = pop @history; next if exists $history{ $temp }; $history{ $temp } ++; unshift @history; }

      Why not follow through when you get to that point? Something like this seems just fine to me:

      my %seen; for (1 .. @history) { my $item = pop @history; unshift @history, $item unless $seen{$item}++; }

      Sure, the temporary variable is still there. But then, do you really prefer something along these lines?

      my %seen; for (1 .. @history) { unshift @history, $seen{$history[-1]}++ ? $#history-- && () : pop +@history; }

      I didn't think so either. ;-)

      — Arien