views:

194

answers:

4

Possible ways:

  1. Using push:

    my @list;  
    push @list, 'foo' if $foo;  
    push @list, 'bar' if $bar;  
    
  2. Using the conditional operator:

    my @list = (  
        $foo ? 'foo' : (),    
        $bar ? 'bar' : (),              
    );
    
  3. Using the x!! Boolean list squash operator:

    my @list = (  
        ('foo') x!! $foo,  
        ('bar') x!! $bar,  
    );  
    

Which one is better and why?

+12  A: 
Sinan Ünür
Agreed, `x!!` is a little too clever for my taste. I'm hoping that it doesn't gain traction as an idiom.
Michael Carman
Actually `!!` converts a false value to a zero length string.
Brad Gilbert
@Brad which yields `0` when used in a place where `perl` expects a number. Fixed now. Thank you.
Sinan Ünür
+5  A: 

I'd like to suggest what I shall dub the "Readable" approach:

sub maybe ($$) { $_[1] ? $_[0] : () }

my @list = (
  maybe("foo", $foo),
  maybe("bar", $bar),
);

It has many of the benefits of this alleged x!! operator (though slightly longer), but with the added bonus that you can actually understand your code when you come back to it later.

EDIT: The prototype doesn't help Perl parse any better and doesn't let us ditch the parenthesis, it just prevents Perl from doing things we don't want. If we want to ditch the parens, we have to do some work. Here's an alternate version that works without parenthesis:

sub maybe {
  my($var, $cond, @rest) = @_;
  return @rest unless $cond;
  return $var, @rest;
}

my @list = (
  maybe "foo", $foo,
  maybe "bar", $bar,
);

No matter what we do with prototypes, Perl will try to parse it as maybe("foo", $foo, maybe("bar", $bar)), so if we want to ditch the parenthesis, we just have to make that give the correct result.

Chris Lutz
+1 Also, parentheses around maybe's args are optional because of proto.
eugene y
@eugene y - That's why I used a prototype, but it's incorrect. Without parenthesis, it would try to parse as `maybe("foo", $foo, maybe("bar", $bar))`. The prototype rejects this, making sure that Perl doesn't try to do something we don't want it to do. Of course, with a little work we could make it work without parenthesis.
Chris Lutz
Thanks for correcting me. Either `maybe("foo", $foo)` or `(maybe "foo", $foo)` can be used. Unprototyped version seem to be an overkill to me :)
eugene y
This is pretty fantastic. Clean, elegant, and perl!
Robert P
It is baaad. It is *un*readable. First, it introduces new and uncommon function, that shouldn't be used in group development, since every new person has to look up the definition of this function. Second, it uses prototypes and so embarrasses every new developer even more. Third, it takes time to get used to it. Fourth, it is not much shorter than basic Perl constructs cited in the question, hence it's not worth to be introduced at all.
codeholic
@codeholic - So if new and uncommon functions shouldn't be used, is all the code you write one long list of calls to built-in functions? I'm sorry, but your assertions are bull. The second one (that I prefer, because I'm crazy) doesn't need prototypes, and the first one only uses them to prevent people from accidentally omitting parenthesis and having it parse incorrectly. In any project, a new developer is going to have to learn a little bit about the internal helper functions and the pre-existing functions to get started, and adding a simple and relatively small one should not be a problem.
Chris Lutz
And your last sentence, "it is not much shorter than basic Perl constructs cited in the question, hence it's not worth to be introduced at all," is just silly. Shorter != more readable. Readability is the entire point. `maybe "foo", $foo` is more readable than `("foo") x!! $foo` by a mile. Anyone with any Perl experience should be able to read the first version, and anyone with any experience in a C-like language should be able to read the second, but anyone who reads the project documentation (which all new developers should do) should be able to read mine, regardless of their Perl skill.
Chris Lutz
@Chris The Perl code I write is not one long list of calls to built-in functions, because I distinguish helper functions and syntactic sugar. And it's still Perl code, because I realize that Perl has enough syntactic sugar (even may be too much) to aggravate it with your own inventions.
codeholic
+1  A: 

Personally I use $foo ? 'foo' : () in such cases, since it is clear (comparing to ('foo') x!! $foo), doesn't require particular effort to be understood (comparing to ('foo') x !!$foo) and can be used in the list context (comparing to push @list, 'foo' if $foo;).

But, of course, the answer depends on what criteria you choose to choose the best option. If you compare them by obfuscation, ('foo') x!! $foo will surely win. If you compare them by clumsiness, push @list, 'foo' if $foo; will be the first. Or maybe you meant performance? I guess not :-)

But if you compare them by good style, my choice is $foo ? 'foo' : ().

codeholic
+2  A: 

Although the OP didn't call for it, this problem provided me with a good excuse to use Benchmark;

Here's the code:

use strict;
use warnings;
use Benchmark qw( cmpthese);

my $foo = 1;
my $bar = "true";

cmpthese( -10, {
  "push" => sub {
      my @list;
      push @list, 'foo' if $foo;
      push @list, 'bar' if $bar;
  },
  "?::" => sub {
      my @list = (
        $foo ? 'foo' : (),
        $bar ? 'bar' : ()
      );
  },
  "x!!" => sub {
      my @list = (
        ('foo') x!! $foo,
        ('bar') x!! $bar
      );
  }
});

And here are some typical results:

         Rate  x!!  ?:: push
x!!  646539/s   --  -8% -12%
?::  701429/s   8%   --  -5%
push 736035/s  14%   5%   --

Given brian d foy's 7% estimate for Benchmark's uncertainty, it definitely seems like push is the fastest way to grow.

Summing up:

$need4speed ? do { push @like $this} : try { something('less' x!! $obfuscated) };
# DISCLAIMER: This is not valid code
Zaid