hubb0r has asked for the wisdom of the Perl Monks concerning the following question:

Monks, I'm writing a package with numerous subs. At one point a sub will look at the data it is given, and make a choice as to which sub to hand it off to to do the work. This sounded like a good job for a dispatch table. I'm used to calling other subs in the same package using the:
$self->sub($var);
notation. I attempted to create the dispatch table like:
my %message_types = ( type1 => \&{$self->sub_1}, type2 => \&{$self->sub_2}, );
and I actually tried a few other notations along the same lines. Each run would result in the value of the hashkey to be undef. The only way I was able to make this work was with an explicit package name like so:
my %message_types = ( type_1 => \&MY::Fully::Qualified::Package::Name::sub1, type_2 => \&MY::Fully::Qualified::Package::Name::sub2, );
The question is, is it possible to use the $self->sub notatation to do this? My assumption was that it was, but you know what they say about assumptions... I'd really rather use the self referencing notation as it looks much cleaner and is much more readable. Thanks for your help!!

Replies are listed 'Best First'.
Re: Dispatch table within package question
by ikegami (Patriarch) on Jan 26, 2005 at 17:39 UTC

    If you want to use $self as defined when %message_types is defined, use:

    my %message_types = ( type1 => sub { $self->sub_1(@_); }, type2 => sub { $self->sub_2(@_); }, ); $message_types{$type}->(@args);

    If you want to use $self as defined when %message_types is used, use:

    my %message_types = ( type1 => 'sub_1', type2 => 'sub_2', ); $method = $message_types{$type}; $self->$method(@args);

    You can also use a hybrid:

    my %message_types = ( type1 => sub { $object->sub_1(@_); }, type2 => sub { $object->sub_2(@_); }, type3 => 'sub_3', type4 => 'sub_4', type5 => \&bla, type6 => \&Foo::bar, ); $func = $message_types{$type}; if (ref($func)) { # It's a function. $func->(@args); } else { # It's a method. $self->$func(@args); }

    Update: Added missing "$" in front of some "func"s.

      This explains a lot, thanks ikegami. I'm still getting my feet wet with using a more oop approach to perl. Your answer and Aristotle's are exactly what I was looking for!

      Presumably, the method which contains the definition of the dispatcher will be called frequently. Is there any performance related disadvantage to this for, say, the case that the dispatcher hash is very large?

      And if I did want to define the dispatcher as a member in the constructor along the lines of:

      my $self->{message_types} = ( type1 => sub { $self->sub_1(@_); }, type2 => sub { $self->sub_2(@_); }, );

      what would the call the dispatcher look like?

      I tried:

      $self->{message_types}->{$type}->(@args);

      but as what I am doing is beyond my understanding of Perl hairiness, I wasn't suprised to find that it doesn't work. What would the correct code look like?

      Thanks,

      loris


      "It took Loris ten minutes to eat a satsuma . . . twenty minutes to get from one end of his branch to the other . . . and an hour to scratch his bottom. But Slow Loris didn't care. He had a secret . . ."

        Perl implements hashes such that lookups are extremely efficient. The size of the hash doesn't matter.

        Your syntax,

        $self->{message_types}->{$type}->(@args);

        should be fine. What's the error? What's your code? Remember, that won't use the current value of $self, but the one $self had when the anonymous sub was created (assuming $self was a lexical (my) variable).

Re: Dispatch table within package question
by Aristotle (Chancellor) on Jan 26, 2005 at 17:44 UTC

    It's not directly possible. However, this is:

    my %handler_for = ( type1 => 'sub1', type2 => 'sub2', ); my $handler = $handler_for{ $type }; $self->$handler( $var );

    Depending on the scope of your problem you should derive a class for each handler, though, and let the method dispatch take care of this.

    Makeshifts last the longest.

Re: Dispatch table within package question
by bpphillips (Friar) on Jan 26, 2005 at 17:55 UTC
    you can also use can to get a code reference to an objects method. i.e.
    sub sub { my ($self,$type,@args) = @_; my %message_types = ( type1 => $self->can("type1"), type2 => $self->can("type2") ); return $message_types{$type}->($self,@args); }
    Make sure that when you call the methods like this (i.e. $message_types{$type}->()) that you take into account that $self won't automatically be part of @_ as if you had called it like $self->method(). You have to explicitly pass it in your dispatcher method

      I prefer:

      return $self->$message_types{$type}->(@args);

      To me, it looks more like a method call.

      Update: Yep, invoking a coderef from a hash is a little too complex for the parser to handle.

        that's fine, unfortunately, I can't get that to compile... Maybe you were thinking something like:
        my $method = $message_types{$type}; $self->$method(@args);
        Which I agree looks more like a method call and avoids having to pass $self manually -- Brian