Senin, 28 Desember 2009

Given/when expression

Given/when in Perl 5.10 is great (and will be even greater in Perl 6, for example we can remove many of the now-required parentheses). But it is very statement-oriented. Sometimes I miss CASE expressions a la SQL:
SELECT store_name, CASE store_name
WHEN 'Los Angeles' THEN sales*2
WHEN 'San Diego' THEN sales*1.5
ELSE sales
END AS new_sales
FROM store;

So, how do you do given/when expressions in Perl? I can think of a couple alternatives, but none is very appealing. Do you a have better one?
my @store = (
{store_name=>'Los Angeles', sales=>100_000},
{store_name=>'San Diego', sales=>100_000},
{store_name=>'Anaheim', sales=>100_000}
);

1. do {} + if/elsif (con: needs do{}, needs explicit assignment to $_, no implicit smart matching)
for my $s (@store) {
printf "%s %d %d\n", $s->{store_name}, $s->{sales}, do {
local $_ = $s->{store_name};
if (/Angel/) { $s->{sales}*2 }
elsif ($_ eq 'San Diego') { $s->{sales}*1.5 }
else { $s->{sales} }
};
}

2. do {} + given/when (con: needs do{}, needs $tmp assignments)
for my $s (@store) {
printf "%s %d %d\n", $s->{store_name}, $s->{sales}, do {
my $tmp;
given($s->{store_name}) {
when (/Angel/) { $tmp = $s->{sales}*2 }
when ('San Diego') { $tmp = $s->{sales}*1.5 }
default { $tmp = $s->{sales} }
}
$tmp
};
}

3. sub {} + given/when (con: needs sub{}, slower perhaps?, needs explicit return's)
for my $s (@store) {
printf "%s %d %d\n", $s->{store_name}, $s->{sales}, sub {
given ($s->{store_name}) {
when (/Angel/) { return $s->{sales}*2 }
when ('San Diego') { return $s->{sales}*1.5 }
default { return $s->{sales} }
}
}->();
}

7 komentar:

  1. I would suggest gather/take, since you're already delving into features inspired by Perl 6. There's a module (Perl6::Gather) that gives you this feature in Perl 5. I can't say how stable it is, but it's worth investigating.

    BalasHapus
  2. why not something like:

    my %store_map = (
    'Los Angeles' => {sales => 100_000, factor => 2},
    'San Deigo' => {sales => 100_000, factor => 1.5},
    'Anahiem' => {sales => 100_000, factor => 1},
    );

    sub get_sales {
    my ($city) = @_;
    return $store_map{$city}->{sales} * $store_map{$city}->{factor};
    }

    BalasHapus
  3. @Carey: thanks for the suggestion, but I'm failing to see how gather/take is appropriate or superior in this case (pun intended). It doesn't give implicit smart matching, for one.

    BalasHapus
  4. @schelcj: admittedly my example is not great, but I think you're missing the point. I'm *not* trying to solve the "store sales" problem :-)

    BalasHapus
  5. As a side note, I found out today that Ruby's case statement can be used in an expression:

    <<'EOE';
    car = "Patriot"

    manufacturer = case car
    when "Focus" then "Ford"
    when "Navigator" then "Lincoln"
    when "Camry" then "Toyota"
    when "Civic" then "Honda"
    when "Patriot" then "Jeep"
    when "Jetta" then "VW"
    when "Ceyene" then "Porsche"
    when "Outback" then "Subaru"
    when "520i" then "BMW"
    when "Tundra" then "Nissan"
    else "Unknown"
    end

    puts "The " + car + " is made by " + manufacturer
    EOE


    It is a nice language after all, isn't? Now that makes it "3 things I wish we had in Perl" (besides symbols and optional semicolons).

    BalasHapus
  6. i fail to see how a huge given/when block -- which is really just another way of writing if/elsif blocks -- is any better then using a hash map.

    BalasHapus
  7. @schelcj: Yes, given/when is merely a syntactic sugar for if/elsif blocks. But it has one nicety: implicit use of smart matching. Also, hash map only works for exact string matching.

    BalasHapus