http://qs321.pair.com?node_id=117975

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

I've got some code that uses quite a few bits of perl cleverness. I'd like to use strict, but
&{'do_'.$type}
gets me the error <code > Can't use string ("do_somestring") as a subroutine ref while "strict refs"</code>
I'd rather not have to use a load of if's to get round this, and I tried untainting $type with a regexp, but it looks like strict just doesn't like the method at all. Is there a prettier way of doing it ?

the hatter

Replies are listed 'Best First'.
Re: Strict, strings and subroutines
by davorg (Chancellor) on Oct 10, 2001 at 16:18 UTC

    I'd do this by creating a hash of references to the various subroutines like this:

    sub do_x { print "processing x\n"; } sub do_y { print "processing y\n"; } my %subs = ( x => \&do_x, y => \&do_y );

    Then when you know what type you're going to be processing, you can do something like this:

    $subs{$type}->();
    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you don't talk about Perl club."

Re: Strict, strings and subroutines
by dragonchild (Archbishop) on Oct 10, 2001 at 16:26 UTC
    Just to give some more weight to one of the choices, I would go with davorg's suggestion to create a dispatch table. That is the type-safest method, because you've presumably validated the functions you're putting into the dispatch table. In addition, it's easier to work with, as the functions are just values in a data structure you can iterate over, etc.

    The no strict 'refs'; suggestion, while the easiest to implement (you just put a block around the call and add no strict 'refs'; at the top) ... it doesn't solve the issue. All you end up doing is patching around the problem. strict is, well, strict ... for a reason.

    The eval suggestion, while canonical, is, in my opinion, also a patch. eval is extremely powerful, but it doesn't give you the greatest amount of control over what's happening. There are a number of cases where you would have to use it, cause there simply isn't any other sane way to do what you have to do. But, if you can do something in a sane and maintainable manner that doesn't use eval, I'd suggest doing that. eval is one of the less-understood features of Perl. The poor schmuck who has to read your code after you're hit by that truck will remember you fondly if you try to minimize his/her learning curve. :-)

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: Strict, strings and subroutines
by gbarr (Monk) on Oct 10, 2001 at 16:49 UTC
    Others have given you several solutions, but there is one more. strict will only cause an error if you try to call the sub by using a string. It does not stop you taking a reference. So

    my $sub = \&{'do_'.$type}; $sub->();

    Is perfectly legal. But if you don't want the extra variable

    (\&{'do_'.$type})->();

    will work too. But the benefit of using the variable is that you can check that the sub actually exists with

    my $sub = \&{'do_'.$type}; die "Unknown sub 'do_$type'" unless defined(&$sub);
Re: Strict, strings and subroutines
by trantor (Chaplain) on Oct 10, 2001 at 16:02 UTC

    Well the error message says it all... however if you know what you're doing (and chances are you can do differently, e.g. through hashes or proper sub references, just do a Super Search):

    #!/usr/bin/perl -w use strict; sub foobar { print "Here I am!\n"; } my $var = 'bar'; no strict "refs"; &{'foo'.$var};

    You can lexically enable and disable strict as you need, always bearing in mind that you must know what you're doing...

    -- TMTOWTDI

Re (tilly) 1: Strict, strings and subroutines
by tilly (Archbishop) on Oct 10, 2001 at 17:24 UTC
    Getting into the gross, this does something slightly different from the above solutions. Though called like a function, it will actually do a method search for it:
    __PACKAGE__->can("do_$type")->();
      I thought about that one too, but it also follows @ISA. You can fix that with this:
      do { local @ISA; __PACKAGE__->can("do_$type") }->();
      Much smoke. Many mirrors. In fact, I'd cache the result too:
      BEGIN { my %cache; sub do_it { my $func = shift; ($cache{$func} ||= do { local @ISA; __PACKAGE__->can("do_$fun") }) ->(@_); } } ... do_it("function", $arg1, $arg2, @more_args);

      -- Randal L. Schwartz, Perl hacker

        Great.

        A pity you don't cache the possibility that it doesn't exist:

        BEGIN { my %cache; sub do_it { my $func = shift; $cache{$func} = do { local @ISA; __PACKAGE__->can("do_$fun") } unless exist $cache{$func}; $cache{$func}->(@_); } }
        Jeroen

        Update: merlyn is right. Fixed that.

        But you still don't avoid pulling in UNIVERSAL. Which is why I left it at, "It does something different." If you don't understand what you are doing differently and don't have a reason for it, well then you don't need to do this. But then again that is true for most of these solutions... :-)
        Changing @ISA at runtime is not always a good thing to do. If your application does use a lot of objects and method calls it will cause the method lookup cache to be cleared. So it will have the affect of slowing your program down. By how much, and if that is an issue really depends on the application though.
Re: Strict, strings and subroutines
by graq (Curate) on Oct 10, 2001 at 16:15 UTC
    You could try using an eval.
    #!/usr/bin/perl -w use strict; my $type = 'hatter'; my $arg = 'using eval'; my $proc = "do_$type"; eval "$proc( \$arg )"; die( "Eval errored: $@" ) if $@; sub do_hatter { my( $foo ) = @_; print "This can be done $foo.\n"; }
    Hope this helps.

    --
    Graq

Re: Strict, strings and subroutines
by George_Sherston (Vicar) on Oct 10, 2001 at 16:38 UTC
    As others have said, strict doesn't lie, so this is likely to be the wrong way to do what you want to do. If what you are trying to do is control the flow of subroutines dependent on input, or something similar, you'll find a lot of Good Ways To Do It on this thread.

    § George Sherston