http://qs1969.pair.com?node_id=492651

All:
Higher Order Perl, by Dominus, has become a very popular book. It was written to teach programmers how to transform programs with programs. Many of us who do not have familiarity with Functional Programming are not aware of what a Higher Order function is. It is a function that does at least one of the two following things:

For some, you can stop reading here because you already know what Higher Order functions are - you just didn't know that's what they were called. In Perl terminology, we often refer to them as callbacks, factories, and functions that return code refs (usually closures). Even if you are familiar with those terms, you may not be familiar with how to use them.

This tutorial is an illustration of how a simple every day function may become higher order, increasing its usefulness in the process. Along the way we will pick up other tricks that can make our code more flexible.

Problem: We have a file containing a list of scores and we need to determine the highest score.

Using the principal of code reuse and not reinventing the wheel, we turn to our trusty List::Util.

use List::Util 'max'; my @scores = <FH>; my $high_score = max(@scores);

Unfortunately, this requires all of the scores to be held in memory at one time and our file is really big. Just this once, we decide to break the rules and roll our own.

my $high_score; while ( <FH> ) { chomp; $high_score = $_ if ! defined $high_score || $_ > $high_score; }

As time goes by "just this once" has happened many times and we decide to make our version reuseable.

sub gen_max { # Create an initial default value (or undef) my $max = $_[0]; # Create an anonymous sub that can be # dereferenced and called externally # but will still have access to $max return sub { # Process 1 or more values for ( @_ ) { $max = $_ if ! defined $max || $_ > $max; } return $max; }; } my $max = gen_max(); while ( <FH> ) { chomp; # Dereference and call the anonymous sub # Passing in 1 value at a time $max->($_); } # Get the return value of the anonymous sub my $high_score = $max->();

This is our first step into Higher Order functions as we have returned a function as the output for the sake of reusability. We also have a few advantages over the original List::Util max function.

Unfortunately, our function breaks the second we start comparing strings instead of numbers. We could make max() and maxstr() functions like List::Util but we want to use the concept of Higher Order functions to increase the versatility of our single function.

sub gen_reduce { my $usage = 'Usage: gen_reduce("initial" => $val, "compare" => $co +de_ref)'; # Hashes need even number of arguments die $usage if @_ % 2; my %opt = @_; # Verify that compare defined and a code reference die $usage if ! defined $opt{compare} || ref $opt{compare} ne 'COD +E'; my $compare = $opt{compare}; my $val = $opt{initial}; return sub { for ( @_ ) { # Call the user defined anonymous sub # Passing in two parameters using the return $val = $_ if ! defined $val || $compare->($_, $val); } return $val; }; } # Create an anonymous sub that takes two arguments # A true value is returned if the first is longer my $comp = sub { return length($_[0]) > length($_[1]); } my $maxstr = gen_reduce(compare => $comp ); while ( <FH> ) { chomp; $maxstr->($_); } my $long_str = $maxstr->();

Now our function takes a function as input and returns a function as output. In addition to the previous functionality, we have added a few more features.

This does not have to be the end of the journey into Higher Order functions, though it is the end of the tutorial. Whenever you encounter a situation where two programs do nearly identical things but their differences are enough to make using a single function impossible - consider Higher Order functions to bridge the gap. Remember - it is important to always document your interface and assumptions well!

I open the floor to comments both on the advantages and disadvantages of Higher Order functions. As they say, there is no such thing as a free lunch and there are always cases in which it makes sense to use distinct routines for distinct problems.

Cheers, L~R

Updates: The second paragraph regarding terminology was added at the suggestion of diotalevi in the CB. Comments have been added to the code at the suggestion of several monks in the CB.

Note: List::Util is a great module and the limitation of requiring all the values to be present at once is usually made up for by the fact that it also provides a reduce() function, has both C and Perl implementations, and syntactic sugar. The limitations were highlighted here for illustration purposes though I recommend using it when and where it does the job you need it to.