Listing 1.
Dynamic Function Calls
Tuomas J. Lukka
Parsing VRML
The Perl Journal, Fall 1998
 

Some of the techniques used in the text might not be obvious to new Perl converts. The three boxes in this article explain some of the ones which might most baffle newcomers. The techniques introduced here are by no means obscure hacks-they are well-documented and stable features of Perl that you can rely on in the future. It's just that they are unexpectedly practical for people who are used to other languages where there is only one way of doing things.

Because Perl is so dynamic, you can do many things while your program is running (run-time) as opposed to when the Perl compiler is reading your program (compile-time). The parser takes advantage of this, letting you choose classes on the fly. Consider this program:

package A; sub foo {print "You called the sub A::foo\n"}
package B; sub foo {print "The routine B::foo was called\n"}
package main; $ARGV[0]->foo();

There are two foo() methods here; which one is called depends on what argument (A or B) you provide on the command line. Even though this little program doesn't explicitly pass any parameters to foo(), Perl adds one for you: all subroutines called in this manner receive is the name of the caller's package as the first parameter. This might seem silly - after all, we know which package the function is in - but there is actually a very good use for this: inheritance.

package A; sub foo {print "You called A::foo through package $_[0]\n"}
package B; @ISA = ('A');
package C; @ISA = ('A');
package main; $ARGV[0]->foo();

In this case, the subroutine A::foo is invoked on any call to A::foo, B::foo, or C::foo. But because of that first parameter, it knows where it was called from, and can operate differently if necessary. This might again seem pointless-why don't we just call A::foo with that argument in the first place? Again, there is a reason for doing it like this. Suppose that A::foo implements the same things asB::foo, but in a slower and more general fashion. In this case, we can first prototype the system with code like the above and later replace B::foo with something quick and specific. The advantage is that we won't have to change any other code. If we had hardcoded A::foo with the parameter, we would now have to either change the calls or make A::foo more complicated. (This is exactly what happened with the FreeWRL code for multiple-valued fields.)

Of course, it is possible to achieve similar things in C++, or Java (using inheritance), or C (using function pointers). However, in Perl the packages do not need to be related in any way before runtime (Java can do that too, but only in a more complicated way). See the perlmod and perlobj documentation for more detail.