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

I'm writing a CGI where I'd like the user to select a layout for a series of thumbnails, and store that selection in a cookie, but let the user override the value stored in the cookie by changing their selection via a link. I thought I'd get away without having several big if/elsif/else blocks (there are a few user-definable parameters) by using the following code:

use CGI qw/:standard/; use CGI::Cookie; #... my %cookies = fetch CGI::Cookie; #... $rows = $cookies{'rows'} ? ( param( 'rows' ) ? param( 'rows' ) : $cookies{'rows'}->value ) : ( param( 'rows' ) and param( 'rows' ) =~ /^\d{1,2}$/ ? param( 'row +s' ) : 5); #... my $rowscookie = new CGI::Cookie( -name => 'rows', -value => $rows, -expires => '+3M', ); #... print header(-cookie=>[$rowscookie,$colscookie]); #...

... which I thought was working fine until I tried it from a browser that didn't already have a cookie, in which I noticed that I get _no_ value in $rows.

I've been scratching my head on this for a little while, and have deduced that I'm having trouble seeing the error of my own ways, and I'm hoping someone else can point a finger at my problem. I've included the other relevant code in case the problem lies outside my nested ternaries.

Note: $colscookie is constructed exactly the same way, and is suffering exactly the same problem.

Update: Not sure if it's obvious from the code, but failing the presense of a cookie value or a link param, I'd like it to default to 5.



--chargrill
$/ = q#(\w)# ; sub sig { print scalar reverse join ' ', @_ } + sig map { s$\$/\$/$\$2\$1$g && $_ } split( ' ', ",erckha rlPe erthnoa stJu +" );

Replies are listed 'Best First'.
Re: Nested ternaries
by GrandFather (Saint) on Feb 15, 2006 at 23:24 UTC
    use strict; #use warnings; sub tt { my ($cookies, $param) = @_; my $str = '(' . (defined $cookies ? $cookies : 'undef'); $str .= ', ' . (defined $param ? $param : 'undef') . "):\t"; $str .= $cookies ? ( $param ? $param : '$cookies{"rows"}->value' ) : ( $param and $param =~ /^\d{1,2}$/ ? 'param( "rows" )' : 5); return $str; } print tt(undef, undef), "\n"; print tt(0, undef), "\n"; print tt(1, undef), "\n"; print tt("0but", undef), "\n"; print tt(undef, 0), "\n"; print tt(undef, 1), "\n"; print tt(undef, 11), "\n"; print tt(undef, 111), "\n"; print tt(undef, "0but"), "\n"; print tt(0, 0), "\n"; print tt(1, 1), "\n";

    Prints:

    (undef, undef): (0, undef): (1, undef): $cookies{"rows"}->value (0but, undef): $cookies{"rows"}->value (undef, 0): 0 (undef, 1): param( "rows" ) (undef, 11): param( "rows" ) (undef, 111): 5 (undef, 0but): 5 (0, 0): 0 (1, 1): 1

    DWIM is Perl's answer to Gödel

      Beautiful, I was having a problem testing all cases, which quickly showed the flaw in my logic, and I believe the and was throwing off the ternary - it has leftward associativity while ?: is rightward. The following changes looks like it will do what I want:

      sub tt { my( $cookie, $param ) = @_; my $str = '(' . (defined $cookie ? $cookie : 'undef'); $str .= ',' . (defined $param ? $param : 'undef') . "):\t"; $str .= $cookie # I quickly realized it was necessary to test this here ? ( ( $param and $param =~ /^\d{1,2}$/ ) ? $param : $cookie +) # and I had to place a few more parens #: ( $param ? ($param and $param =~ /^\d{1,2}$/) : 5 ); # UPDATE: the above line was horribly translated from Grandf +athers. # It is corrected below, which also works when the same nest +ed # ternary idea (substituting /^\w{1,6}$/ for example) is use +d to # parse textual data. : ( ( $param and $param =~ /^\d{1,2}$/ ) ? $param : 5 ); # END UPDATE $str .= "\n"; return $str; } print tt( 4, 3 ); print tt( 4, 333 ); print tt( 444, 333 ); print tt( 2, undef ); print tt( undef, 1 ); print tt( undef, undef );

      Prints:

      (4,3): 3 (4,333): 4 (444,333): 444 # famous last words, but "this can never happen" ; +) (2,undef): 2 (undef,1): 1 (undef,undef): 5

      ++Grandfather, terse though your reply was (lacking explanation but sufficient code to guide me to the solution), I thank you for that quick sanity check.



      --chargrill
      $/ = q#(\w)# ; sub sig { print scalar reverse join ' ', @_ } + sig map { s$\$/\$/$\$2\$1$g && $_ } split( ' ', ",erckha rlPe erthnoa stJu +" );

        I decided the code was sufficient to stand on its own - seems that it did. Glad you liked it :)


        DWIM is Perl's answer to Gödel

        Continually irritated at the ugliness of the nested ternary once placed back into my code:

        $scheme = $cookies{'skin'} ? ( ( param( 'scheme' ) and param( 'scheme' ) =~ /^\w{1,6}$/ ) ? param( 'scheme' ) : $cookies{'skin'}->value ) : ( ( param( 'scheme' ) and param( 'scheme' ) =~ /^\w{1,6}$/ ) ? param( 'scheme' ) : 'default' );

        Along with an offline suggestion from liverpole of an operator (||=) I had forgotten yields the following test code:

        use strict; use warnings; sub ttw { my( $cookie, $param ) = @_; my $str = '(' . (defined $cookie ? $cookie : 'undef'); $str .= ',' . (defined $param ? $param : 'undef') . "):\t"; $str .= $cookie ||= $param && $param =~ /^\w{1,6}$/ ? $param : 'defa +ult'; $str .= "\n"; return $str; } sub ttd { my( $cookie, $param ) = @_; my $str = '(' . (defined $cookie ? $cookie : 'undef'); $str .= ',' . (defined $param ? $param : 'undef') . "):\t"; $str .= $cookie ||= $param && $param =~ /^\d{1,2}$/ ? $param : 5; $str .= "\n"; return $str; } print ttw( 'yellow', 'blue' ); print ttw( 'green', undef ); print ttw( undef, 'teal' ); print ttw( undef, 'chartreuse' ); print ttw( undef, undef ); print ttd( undef, undef ); print ttd( 4, undef); print ttd( 444, undef); print ttd( undef, 3); print ttd( undef, 333); print ttd( 6, 7);

        ... which prints:

        (yellow,blue): yellow (green,undef): green (undef,teal): teal (undef,chartreuse): default (undef,undef): default (undef,undef): 5 (4,undef): 4 (444,undef): 444 (undef,3): 3 (undef,333): 5 (6,7): 6

        And finally the finished code re-worked into my original CGI looks much more succint:

        $scheme = $cookies{'skin'} ||= param( 'scheme' ) && param( 'scheme' ) =~ /^\w{1,6$/ ? param( 'scheme' ) : 'default';

        ... but doesn't work. So I try:

        $scheme = $cookies{'skin'}->value ||= param( 'scheme' ) && param( 'scheme' ) =~ /^\w{1,6$/ ? param( 'scheme' ) : 'default';

        ... which throws an error, Can't modify non-lvalue subroutine call at /path/to/viewer.cgi line 74. And now I can't imagine a way to re-work the ||= operator into my code. Which leaves me with my original, working, ugly version.

        I guess my point is that it's fine to simplify code for the purposes of testing, but be wary of shortcuts (golfing) taken to eliminate what might appear to be unnecessary syntax in the test version.



        --chargrill
        $/ = q#(\w)# ; sub sig { print scalar reverse join ' ', @_ } + sig map { s$\$/\$/$\$2\$1$g && $_ } split( ' ', ",erckha rlPe erthnoa stJu +" );