Siaris

Subroutine References
16 Aug 01 - http://www.siaris.net/index.cgi/Programming/LanguageBits/Perl/20010816.rdoc

We have used subroutine references a couple of times in previous articles, but we haven’t taken a look at them directly.

You can take a reference to an existing (named) subroutine using the backslash operator along the ampersand in the following manner:

    sub foo {
        print "This is foo\n";
    }

    my $sref = \&foo;

You can call the referenced routine by preceding the scalar holding it with an ampersand, or by using the dereference arrow and parentheses:

    &$sref();     # or:  &$sref('argument')
    $sref->();    # or:  $sref->('argument')

It is important to realize that you cannot take a reference to a subroutine and pass it arguments at the same time:

    my $sref = \&foo('argument');

What this actually does is return a reference to the return value of that function (using the given argument), which probably isn’t what you wanted.

But what are subroutine references good for? A couple of common uses are dispatch tables and passing routines as arguments to other routines — we will consider the first here. A dispatch table is simply a table (usually a hash) that allows you to select which routine to run. Consider a user interface that queries the user to enter a command to run:

    #!/usr/bin/perl -w
    use strict;

    sub this { print "You picked 'this'\n" }
    sub that { print "You picked 'that'\n" }
    sub quit { exit }

    print "Enter a command:\n";
    while (<STDIN>) {
        chomp(my $cmd = $_);
        if ($cmd eq 'this') {
            this();
        } elsif ($cmd eq 'that') {
            that();
        } elsif ($cmd eq 'quit' or $cmd eq 'exit') {
            quit();
        } else {
            print "Unrecognized command: $cmd\n";
        }
    }
    __END__

Now imagine that there are many more possible commands the user could enter and you can see that the program would grow quite large. Using a dispatch table simplifies all of the logic of the if/elsif statements into the datastructure:

    #!/usr/bin/perl -w
    use strict;

    sub this { print "You picked 'this'\n" }
    sub that { print "You picked 'that'\n" }
    sub quit { exit }

    my %dispatch = (
        this => \&this,
        that => \&that,
        quit => \&quit,
        exit => \&quit,
        );

    print "Enter a command:\n";
    while (<STDIN>) {
        chomp(my $cmd = $_);
        if ($dispatch{$cmd}) {
            $dispatch{$cmd}->();
        } else {
            print "Unrecognized command: $cmd\n";
        }
    }
    __END__

This program is actually a little longer, but it is simpler in design and simpler to maintain and update — adding new commands involves only defining a subroutine and adding another entry to the hash (we don’t have to add further elsif clauses as we would with the first example.

*****