Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

perl code blocks

Tags:

perl

I have a question regarding code blocks in perl. Given the following code:

    my @newArr = sort { $a <=> $b } @oldArr;

uses a code block as an argument.

I could rewrite it as:

    sub sortFunc {
        return $a <=> $b;
    }
    my @newArr = sort sortFunc @oldArr;

I am trying to figure out how this mechanism works. Currently I need to implement a sort of complex sort function that would look messy in a code block, However it depends on some local variables. For example:

   foreach my $val (@values){
       my @newArr = sort { $hash{$a}{$val}<=> $hash{$b}{$val} } @oldArr;
       ...
   }

but lets assume that the sort function is more complex, so it wont neatly fit into the code above.

If I try to use a function(defined locally in the scope of the for loop) , I keep getting "Use of uninitialized value in hash element".

I assume that's because the sub is parsed once, and not recreated for evry iteration of the for loop. I am trying to understand how to implement a code block that would be reinterpreted every iteration, or maybe how to pass parameters

like image 485
Smartelf Avatar asked May 08 '12 15:05

Smartelf


3 Answers

You didn't show the problematic code for some reason, but I think it's something like

for my $val (@values) {
   sub sort_func {
      return $hash{$a}{$val} <=> $hash{$b}{$val};
   }

   my @newArr = sort sort_func @oldArr;
}
I am trying to figure out how this mechanism works. [...] I assume that's because the sub is parsed once, and not recreated for evry iteration of the for loop.

Not quite. The following only parses and compiles the sub once, yet it works:

for my $val (@values) {
   my $cmp_func = sub {
      return $hash{$a}{$val} <=> $hash{$b}{$val};
   };

   my @newArr = sort $cmp_func @oldArr;
}

What matters is when what $val is captured. $val is captured when sub { ... } is evaluated. Keeping in mind that

sub foo { ... }

is the same as the following in this regard,

BEGIN { *foo = sub { ... }; }

In my code, it captures the $val from the foreach loop. In yours, it captures at compile time, so it captures the $val that existed at compile time. And that's not the variable you want.

Now that you know how to make it work, you can move the complex code out of the way (out of the loop) as you desire.

sub make_cmp_func {
   my ($hash, $val) = @_;
   return sub {
      return $hash->{$a}{$val} <=> $hash{$b}{$val};
   };
}

for my $val (@values) {
   my $cmp_func = make_cmp_func(\%hash, $val);
   my @newArr = sort $cmp_func @oldArr;
}

Alternatively, you can pass the necessary values to the compare function instead of capturing them.

sub cmp_func {
   my ($hash, $val, $a, $b) = @_;
   return $hash->{$a}{$val} <=> $hash{$b}{$val};
}

for my $val (@values) {
   my @newArr = sort { cmp_func(\%hash, $val, $a, $b) } @oldArr;
}
like image 189
ikegami Avatar answered Sep 28 '22 04:09

ikegami


You want to use a function that takes arguments in addition to $a and $b.

sub my_sort_func {
    my ($val, $a, $b) = @_;
    return $hash{$a}{$val} <=> $hash{$b}{$val};
}

foreach my $val (@values) {
    my @newArr = sort { my_sort_func($val,$a,$b) } @oldArr;
    ...
}

Perl's mechanism for using a code block with sort is somewhat special, and not easily replicated in pure Perl.

like image 20
mob Avatar answered Sep 28 '22 04:09

mob


Extending on mob's answer, this is one of the "clever but not necessarily smart" variety. If you object to the extra parameter, you can use currying instead.

sub make_sorter {
    my ($hashref, $val) = @_;
    return sub {
          $hashref->{$a}{$val} <=> $hashref->{$b}{$val}
    };
}

for my $val (@values) {
    my $sorter = make_sorter(\%hash, $val);
    my @newArr = sort $sorter @oldArr;
}

This isn't more efficient, more readable, or more valuable in really any way, but it might be interesting to know about the technique for someplace where it's actually useful.

like image 40
hobbs Avatar answered Sep 28 '22 03:09

hobbs