Currying

Until recently, while I thought currying was kind of cool, I did not really understand what the use of it was. I call a function and I am done with it. It was not until I was cleaning up some code in my football predictor project that I started to understand how currying could make your life easier.

Let us say that you have a function that takes an input and based on that input does some calculations. Now, this is normal in Perl. For instance,

sub foo {
    my ($choice) = @_;
    my @float_lst;
    my $total;

    if($choice eq "win") {
        @float_lst = map { run_prediction($_) } (1..10);
        $total += $_ for @float_lst;
    } elsif($choice eq "lose")
        @float_lst = map { run_prediction($_) } (-1..-10);
        $total += $_ for @float_lst;
    } elsif($choice eq "draw") {
        @float_lst = map { run_prediction($_) } (0);
        $total += $_ for @float_lst;
    }
    return $total;
}

You probably notice that you repeat a rather long line of statements. In Ocaml, you can use currying to cut down on those (I am sure there are ways of doing this in Perl as well).

 type outcome = Win | Lose | Draw

 let wins = [1;2;3;4;5;6;7;8;9;10]
 let draw = [0]
 let lose = [-1;-2;-3;-4;-5;-6;-7;-8;-9;-10]

 let calc_outcome outcome =
     let calc_outcome_aux = BatList.map run_prediction in
     let fold_float = List.fold_left (+.) 0. in
         match outcome with
             | Win ->
                 fold_float (calc_outcome_aux wins)
             | Lose ->
                 fold_float (calc_outcome_aux lose)
             | Draw ->
                 fold_float (calc_outcome_aux draw)

Notice how I have called BatList.map and List.fold_left without the required number of arguments. This is not an “off by one error”. What the compiler does is break down the function into a set of single argument functions that it then stores the next in the chain in the lexical bindings. Once you have fulfilled the required number of arguments, it will return its answer. Of course, you can imagine using map over a function that has more than one argument that returns a list of functions that have the other required arguments left to fulfill. In addition, you can call functions where you know one argument will take a long time to compute so you can do the quick arguments first then compute the heavy argument.

In this case, I was able to cut down the length of a line and repeated statements and reuse them based on user input.

4 Comments

You curry by hand in Perl if you’d like, or you can use something like Data::Util::Curry to make it easier.

You don’t need currying to simplify that:

    sub foo {
        my ($choice) = @_;
        my %choices = (
            win  => [1 .. 10],
            lose => [-1 .. -10],
            draw => [0],
        );
        my $total = 0;
        my @float_lst = map { run_prediction($_) } @{$choices{$choice}};
        $total += $_ for @float_lst;

        return $total;
    }
}

Of course, you could also get rid of float_list entirely by doing

$total += run_prediction($_) foreach @{$choices{$choice}};

(-1..-10) will not buy you much good.

Here’s one way to add some spice:

sub run_prediction {
  rand $_
}

sub make_prediction_runner {
  my (@range) = @_;
  return sub {
    map { run_prediction($_) } (@range)
  }
}

my %choice_range_map = (
    'win' => [1..10],
    'lose' => [reverse -10 .. -1],
    'draw' => [0],
);

{
no strict 'refs';   
  while (my ($choice,$range) = each %choice_range_map ){    
  *{"$choice"} = make_prediction_runner(@$range);
  }
}

sub foo {
  my $choice = shift;
  my $total ;
  $total += $_  for &{"$choice"}();
  $total
}

print "$_: ${\(foo($_))}\n" foreach (qw (win lose draw));


There are loads of other ways to arrange all this. For the definitive story on Perl’s functional capabilities see “Higher Order Perl” by Mark Jason-Dominus.

Leave a comment

About cyocum

user-pic Celticist, Computer Scientist, Nerd, sometimes a poet…