Subroutines, Coderefs, and Closures

G. Wade Johnson

Houston.pm

What is a Coderef?

A Short Diversion

References Refresher (cont.)

Making References

   my $sref = \$scalar;
   my $aref = \@array;
   my $aref2 = [ 1, 2, 3 ];
   my $href = \%hash;
   my $href2 = { a => 1, b => 2 };
   my $cref = \&func;

Using References

   print "${$sref}\n";
   my $len = @{$aref};
   $aref->[0] = 15;
   my $keyscnt = keys %{$href};
   $href->{'c'} = 3;

More on References

Back to Subroutines

Subroutine/Coderef Example

   sub sum
   {
       my $sum = shift;
       $sum += $_ foreach @_;
       return $sum;
   }

   my $sumref = \∑
   print $sumref->( 1..6 ), "\n";

Subroutine/Coderef Example 2

   my $sumref = sub
   {
       my $sum = shift;
       $sum += $_ foreach @_;
       return $sum;
   };

   print $sumref->( 1..6 ), "\n";

Alternate Coderef Calling

You can also call through a coderef like this:

   &{$cref}( 1..6 );

I really don't like this style, but it's here for completeness.

What's the point?

Why use coderefs?

Passing Code to a Subroutine

List Operators

   @list = sort { $b <=> $a } @list;

   sub backwards { return $b cmp $a; }
   @list = sort backwards @list;

The code in the curlies is actually a coderef. Also can use the name of a subroutine.

Callbacks

    my $quit_btn = $window->Button(
        '-text' => "Quit",
        '-command' => sub { exit( 0 ); }
    );

Supplying a callback to be executed when the button is clicked.

Containers

How do you do things generically with complex data structures?

Let's do this with a concrete example: a hierarchical filesystem

Find Large Files

    sub find_large_files
    {
        my ($dir, $size) = @_;
        my @files = grep { -s $_ > $size } $dir->files();
        foreach my $d ($dir->dirs())
        {
            push @files, find_large_files( $d, $size );
        }
        return @files;
    }

Find Small Files

    sub find_small_files
    {
        my ($dir, $size) = @_;
        my @files = grep { -s $_ < $size } $dir->files();
        foreach my $d ($dir->dirs())
        {
            push @files, find_small_files( $d, $size );
        }
        return @files;
    }

What's Common

The Problems

A Coderef Version

    sub grep_files
    {
        my ($dir, $cref) = @_;
        my @files = grep { $cref->( $_ ) } $dir->files();
        foreach my $d ($dir->dirs())
        {
            push @files, grep_files( $d, $cref );
        }
        return @files;
    }

Using Coderef Version

    my @large_files =
        grep_files( $dir, sub { -s $[0] > $bigsize } );

    my @small_files =
        grep_files( $dir, sub { -s $[0] < $smallsize } );

Storing Code in a Data Structure

List of Coderefs

    foreach my $cref (@code)
    {
        @data = $cref->( @data );
    }

Notice that we are looping over the code routines not over the data.

Table-Driven Code

Part of a four-function calculator.

  my %operations = (
      '+' => sub { return $_[0] + $_[1]; },
      '-' => sub { return $_[0] - $_[1]; },
      '*' => sub { return $_[0] * $_[1]; },
      '/' => sub { return $_[0] / $_[1]; },
      'q' => sub { exit( 0 ); },
  );

You could actually map any strings. Good also for language-based interfaces.

Code and Data

  my $obj = {
      'name' => 'Fred',
      'age' => 37,
      'command' => sub { return $_[0] * 2; },
  };

Hard to come up with a generic example of this. I have used it several times, so it's funny that I can't come up with a good example.

Unnecessary Conditional

    foreach my $file (@files)
    {
        if($is_relative)
        {
            push @files, $file
                if -s "$base_dir$file" > $size;
        }
        else
        {
            push @files, $file
                if -s $file > $size;
        }
    }

Notice that the condition will be tested for each item in the list. For a complex conditional, this could be expensive. Worse, if there are multiple conditions, the code would be really hard to understand.

Remove Unnecessary Test

    my $test;
    if($is_relative)
    {
        $test = sub { -s "$base_dir$_[0]" > $size ? ($_[0]) : () };
    }
    else
    {
        $test = sub { -s $_[0] > $size ? ($_[0]) : () };
    }
    foreach my $file (@files)
    {
        push @files, $test->( $file );
    }

The code doesn't look much smaller. However, the conditions at the beginning are not executed every time. We can also move this code elsewhere to make this code easier to maintain.

Closures

Classic Closure Example

    sub make_multiplier
    {
        my $factor = shift;
        return sub { return shift * $factor; };
    }

    my $doubler = make_multiplier( 2 );
    my $tripler = make_multiplier( 3 );

This seems to be the way most people introduce closures. Not because it is particularly useful or realistic. I think it is just easy to understand.

Uses for Closures

Currying

Curry Example

    sub add { return $_[0] + $_[1]; }

    sub bind_second
    {
        my $coderef = shift;
        my $second = shift;
        return sub { return $_[0] + $second; };
    }

    my $inc = bind_second( \&add, 1 );

Once again, not particularly useful, but it is easy to understand.

Conclusion