Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Creating subroutines on the fly

by pander (Acolyte)
on Feb 16, 2005 at 16:10 UTC ( [id://431623]=perlquestion: print w/replies, xml ) Need Help??

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

Hi, I was wondering if it is possible to have a subroutine stored as a string and then used as a normal subroutine within a program at run-time. For example, I want to be able to store a subroutine, either named or anonymous, inside a database for the purpose of being able to extend some code. At run time the subroutine is pulled out of the database via DBI and stored in a scalar as a string. How can I then get my program to execute that subroutine?

For example, if in the database I had a column that contained the string...

sub { my $name=shift; return substr($name,0,1); }

How could I get a perl program to use this at run-time assuming it was pulled from the database using DBI into a scalar?

Replies are listed 'Best First'.
Re: Creating subroutines on the fly
by TedYoung (Deacon) on Feb 16, 2005 at 16:15 UTC

    Sure, you can store perl code as a string in a file or a database. Here is an example of how to turn it into a sub.

    $code = "print 'Hello World.'"; # This may have come for your DB $sub = eval "sub { $code }"; # use eval and string interpolation to co +nvert it into an anon. sub $sub->(); # Execute the sub; # To pass arguments to the sub, do this: $sub->($a1, $a2); #etc # You can capture the return value too $ret = $sub->();

    Update: I almost forgot to warn you: be very careful with who has access to the string version of the perl code. Obviously, if that data came from an untrusted source, they could destroy your system!

    Ted Young

    ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)
      Dynamic code is also very hard to debug. I would recommend something like the following, to help you find out when/where it has errors.
      $code = "print 'Hello World.'"; $sub = eval "sub { $code }"; unless ($sub) { # ack! it failed to eval # lets leave a note about the code, and the error # so we can debug this. die sprintf('CODE: %s\n\nERROR: %s\n\n', $code, $@); }

      If you want to name your subroutines, then just stick in a name at the right spot.

      sub make_sub { my ( $sub_name, $code ) = @_ ; local $@; eval "sub $sub_name { $code }" ; die "make_sub $sub_name: $@\n" if $@; } make_sub( say_hello => 'print "Hello @_\n"' ); say_hello( 'World' ); __END__ Hello World

      Note that this will throw wierd warnings if you try to create two subs with the same name. You can work around those....

      ...but chances are that you don't really need to use eval for whatever you're trying to do. You can probably get away with writing a module which contains all the functions you want to store in your database, and then just store the function names in the database instead. Have your program fetch the right name from the database, then then call it.

      Here's your stored functions.

      package MyFunctions; # save this as MyFunctions.pm sub say_hello { shift ; # discard class name: perldoc perlboot print "Hello @_\n" } sub list_dir { shift ; # discard class name require Cwd; my $owd = Cwd::cwd(); my $dir = shift; chdir $dir or die "chdir $dir: $!\n"; print $_ . "\n" while <*> ; chdir $owd; } 1;

      And here's your program. Here, rather than fetching function names and function arguments from a database, we'll just get it from the user.

      #!/usr/bin/perl # save this as program.pl use strict; use warnings; use MyFunctions; print "action> " ; chomp( my $action = <STDIN> ); print "args> "; chomp( my $arg = <STDIN> ); MyFunctions->$action( $arg );

      And here's how it might look when you run it

      $ perl program.pl action> list_dir args> /tmp foo bar baz

      The advantage of storing all your functions in a file and calling them as class methods (i.e. MyFunction->say_hello() rather than MyFunction::say_hello()) is that you have control over what your program does. If your program fetches code from a database, or gets it from the user, then you no longer have control over your program's behavior.

      Belden
Re: Creating subroutines on the fly
by dimar (Curate) on Feb 16, 2005 at 18:48 UTC
    I was wondering if it is possible to have a subroutine stored as a string and then used as a normal subroutine within a program at run-time.

    Have you given sufficient consideration as to why you want to do that? Have you considered any alternatives? If the answer is 'yes' and 'yes' or if you are determined to go this route, you may stop reading this now.

    Pulling and executing code directly from a database like that is bound to cause maintenance headaches. It might be more advantageous for you to specify a 'skeleton' subroutine in your code and 'clone' it if you want to define similar subroutines at a later time. The benefit of this approach is that you minimize the domain of valid inputs to your code Basic Coding Tips: Parsimonious Parameterization, and thereby simplify debugging and interpreting the acceptable range of outputs. Even inventing your own 'mini language' would be better than simply pulling in subroutines as strings, because you are basically saying you want your inputs to be any concievable set of perl instructions ... that can't be good.

    To translate all this abstract talk into a perl code example, here is an example that creates numerous subroutines that get 'cloned' into your perl code at runtime.

      I can imagine a lot of reasone why you want to do this. And this is actually a feature I like much at Perl. Even though I do not use it very often.

      But you have to consider performance issues, if you have evaluated code inside the performance driver (i.e. critical loop) of your script. When Perl sees a variable in your evaluated string, it recompiles it every time . From my current knowledge, I think Perl does not know, if the variable and therefore the string has changed. If not, there is no need to recompile it. But I do not know, if I am up-to-date here.

      Of course, there are solutions for this issue and I do not know, if performance aspects are critical for pander. I just wanted to mention it, since I one was at such a point of Perl programming: Performance tuning for evaluated code segments.

Re: Creating subroutines on the fly
by Anonymous Monk on Feb 16, 2005 at 17:12 UTC
    It can be done with eval:
    #! /usr/bin/perl -w my $a = 'sub { $name = shift; return substr($name, 0, 1); }'; my $b = eval $a ; print &$b("wiredrat");
    However, i don't know if it can be done without eval-ing it into $b. Hope this help you.

    2005-02-16 Janitored by Arunbear - added code tags, as per Monastery guidelines

Re: Creating subroutines on the fly
by artist (Parson) on Feb 16, 2005 at 17:17 UTC
    While it is possible as mentioned, I like to prefer a library of sub routines with specific names, that you can just include in your calling program. It may be easier to maintain and can provide less fear of wrong-doing or less debugging.

      You extend this a bit, and you have what is called an "API". You extend it further, and you get a mini language.

      IMO, the original post isn't quite screaming (maybe loudly speaking) for a mini language, but if robustness is an important issue, a mini language may be in order. Using eval is one thing, and may be great if all interactions to that column in the db is through a perl programmer. Using a mini language allows you to go the extra step and allow anyone to update it, assuming you have enough flexibility in your library of subroutines.

      hi you dont need to use eval although it is probably safer,
      my $subref = sub {print "hello"}; &$subref();
      artist wrote:
      While it is possible as mentioned, I like to prefer a library of sub routines with specific names, that you can just include in your calling program. It may be easier to maintain and can provide less fear of wrong-doing or less debugging.

      Fellow artist
      I see this as a distributed library implementation. Would love to know about better ways of implementing this, so I can use this to my own projects.

      Maybe the better approach would be to store and retrieve modules from the database, and overload use or require to correctly load your module from the database.

      Anyway, I would love to read more about this approach, and about distributed shared perl modules.

Re: Creating subroutines on the fly
by saskaqueer (Friar) on Feb 17, 2005 at 07:55 UTC

    Everybody else seems to be providing my $sub = eval "sub { $code }";. I've never used this method; heck, I've never even thought of doing it that way. It just seems unnatural to me to have it strung along in that manner. I do it the other way around: my $sub = sub { eval $code };. Besides, doing it this way allows for simpler error handling.

    #!/usr/bin/perl -w use strict; my $code = q{ open( my $fh, '<', 'foo.txt' ) or die("open failed: $!") + }; my $sub = sub { eval($code) or die("error in eval(): $@"); }; $sub->();

    The obvious downside is that this will only work for anonymous subs, and not named ones. The only way I can think of doing anything near 'named subs' is to abuse references, which is ugly:

    #!/usr/bin/perl -w use strict; my $code = q{ open( my $fh, '<', 'foo.txt' ) or die("open failed: $!") + }; my $sub = sub { eval($code) or die("error in eval(): $@"); }; my $sub_name = 'do_this'; { no warnings 'once'; no strict qw(refs vars); $$sub_name = $sub; $do_this->(); }

      Putting the eval inside a sub can be must slower if that sub is called many times (more than once) since Perl will have to recompile $code each time the sub is called.

      my $sub = eval "sub { $code }"; compiles the Perl code once, regardless of how many times you execute it.

      Ted Young

      ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)
Re: Creating subroutines on the fly
by Fletch (Bishop) on Feb 17, 2005 at 16:36 UTC

    Something I don't think's been mentioned yet: remember that doing this won't preserve the closure-ness / binding of lexical variables.

    And while possibly backwards from what you're trying to do you might look at Re: How to store CODE ref.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://431623]
Approved by Tanktalus
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (None)
    As of 2024-04-25 00:02 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      No recent polls found