Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to conditionally include an element in a list?

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?

like image 327
Eugene Yarmash Avatar asked Feb 19 '10 11:02

Eugene Yarmash


4 Answers

Well, they all do different things.

However, all other factors being equal,

push @list, 'foo' if $foo;

is the statement that conveys its meaning most directly, so it should be preferred.

If you have to pause and think about statement that supposedly does something as simple as pushing an array element, you are doing something wrong.

my @list = (
    $foo ? 'foo' : (),
    $bar ? 'bar' : (),
);

could be OK if this is part of some colossal initialization that is being done elsewhere.

I think using

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

indicates that the programmer has —how can I put this?— issues.

Incidentally, there is no such thing called the x!! composite operator. The !! is double logical negation. It converts an arbitrary false value to a defined but false value (the empty string) which yields 0 when used where perl expects a number. A true value is converted to a 1. Here, !! operates on $foo and $bar and writing it x!! $foo unnecessarily obfuscates the meaning of the code.

x is the repetition operator. So,

 ('foo') x !!$foo;

means repeat ('foo') once or not at all, depending on whether $foo is true or false, respectively.

PS: Of course, it turns out there was a PerlMonks article introducing the so-called boolean list squash operator. I read the article and find it unconvincing.

like image 84
Sinan Ünür Avatar answered Nov 01 '22 01:11

Sinan Ünür


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.

like image 39
Chris Lutz Avatar answered Nov 01 '22 00:11

Chris Lutz


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
like image 2
Zaid Avatar answered Nov 01 '22 01:11

Zaid


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' : ().

like image 1
codeholic Avatar answered Nov 01 '22 01:11

codeholic