Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

gnat (29)

gnat
  (email not shown publicly)

Journal of gnat (29)

Wednesday March 17, 2004
04:48 AM

Function Pointers

[ #17933 ]
When I taught references, part of the class was always how to do these types of fun things Whether you're a C programmer or a Perl hacker using references, grokking function pointers ("code references" in the Perl world) will improve your coding.

For example, your basic (and I mean basic, though not BASIC) adventure game is just:

%choices = ( "north" => \&go_north,
                    "south" => \&go_south,
                    "east" => \&go_east,
                    "west" => \&go_west,
                    "quit" => \&bail );
while (<>) {
  chomp;
  if (exists $choices{$_}) {
    $choices{$_}();   # call the function
  } else {
    print "I'm sorry, I don't know how to: $_\n";
  }
}

sub go_north {
  print "You trudge northward.\n";
}
# etc for go_east, go_west, go_south
sub bail {
  print "Goodbye!\n";
  exit;
}

Those of you who took my Perl classes may remember that :-) Keys of the hash are commands. The value for a command is a reference to the function to run when the user types that command. If I want to add aliases, I can just say:

$choices{s} = $choices{south};
$choices{widdershins} = $choices{east};

Or generate a lot of aliases automatically:

foreach my $cmd (keys %choices) {
  $choices{substr($cmd, 0, 1)} = $choices{$cmd};
}

Try doing that with the "easy" approach:

while (<>) {
  chomp;
  if ($_ eq "north") {
    print "You trudge northward.\n";
  } elsif (...) {
    ...
  } elsif ($_ eq "quit") {
    print "Goodbye!\n";
    exit;
  } else {
    print "I'm sorry, I don't understand: $_\n";
  }
}

It's a real bugger to add aliases to that.

Callbacks:

sub apply_to_lines {
  my ($filename, $code) = @_;
  local $_;

  open my $fh, "<", $filename or die "Can't open $filename: $!";
  while (<$fh>) {
    $code->($_);
  }
  close $fh;
}

$line_count = 0;
apply_to_lines( "/etc/passwd", sub { $line_count++ } );
print "/etc/passwd has $line_count lines\n";
$longest_line = '';
apply_to_lines( "/etc/passwd", sub {
  $line = shift;
  $longest_line = $line if length($line) > length($longest_line);
});
print "The longest line is: $longest_line\n";

The same subroutine, apply_to_lines, can be used to both count lines or find the longest line. That's the fundamental idea of callbacks: customizing your code by letting the user supply an action, not just a value or variable to store a value into.

Of course, you can do cooler stuff in Perl with code refs than you can in C with function pointers. For example, mess with the symbol table ...

foreach my $colour (qw(red green blue black nastybrown)) {
  no strict 'refs';
  *$colour = sub { print "<font color=$colour>@_</font>" };
}

*$colour means "the symbol table entry for the symbol whose name is in $colour". So the first time through the loop it's "the symbol table for the symbol red", then it's "the symbol table entry for green", etc. By assigning a code reference to that symbol table entry, you change what Perl thinks is the function called red, green, etc. This kind of messing with Perl's head is what Damian and Dominus do five times before breakfast.

End result is that we define a bunch of subroutines, each of which prints some different HTML. If you haven't thought of it, it's fun to realize that because $colour is a private (my) variable, it is allocated new memory each time through the loop and so each subroutine refers to a different value of $colour, whereas @_ is a global variable and so the automatically generated red, green etc. subroutines use whatever value @_ has when the subroutine is called. Once you've figured out what that meant, figure out what happens when you take my out of the foreach loop.

And so on. Lots more uses, no doubt explained in the Alcapacapal, but ENOSLEEP. Jenine's written her epistles to her friends ("First Coloradoans, Chapter 2, Verse 3: Raley didst try me verily, and I didst want to smite her. Vengeance will be mine, sayeth the Mommy, especially if you don't stop flipping those bloody light switches!") and it's bedtime. Ninight.

--Nat

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • I think that gcc's nested functions are fun. I wish they'd been
    added to the C99 standard....

    A fun ML thing is partially applied functions: If foo takes two
    args, a and b, then (foo a) is the same as (fun b -> foo a b).
    Of course, to do this you need static typing.
  • Or even more easily generated choices:
    foreach my $cmd (keys %choices) {
      for my $i (1..length $cmd) {
        $choices{substr($cmd, 0, $i)} = $choices{$cmd};
      }
    }
    N, NO, NOR, NORT, NORTH....
    --

    --
    xoa

    • That will only work as long as the choices are unique on the first letter.

      It would be much better to use the Text::Abbrev module (it's part of the core). It'll create unique abbreviations. Of course the code would be a little more convoluted:

      use Text::Abbrev;
      my %abbrev_choices;
      my %abbrevs = abbrev(keys %choices);
      foreach my $abbrev (keys %abbrevs) {
        $abbrev_choices{$abbrev} = $choices{$abbrevs{$abbrev}};
      }
      %choices = %abbrev_choices;

      (or something like that - completely untested and of my hat)

    • Or, rather than filling the hash with small keys, make a smarter dispatcher:

      # find those commands which could match
      @cmd = grep { /^$cmd/ } keys %cmd;
      # die if none or several commands match
      die "Ambiguous command $cmd (@cmd)" if @cmd != 1;
      # call the routine
      $cmd{shift@cmd}(@args);

      Mmm, $cmd, @cmd and %cmd used in 3 lines... That's one point in the Perl purity test. :-)