Search code examples
perlevalperl-data-structures

Creating subs on the fly from eval-ed string in perl


I need to transform data structures from a list of arrays into a tree-like one. I know the depth of the tree before I start processing the data, but I want to keep things flexible so I can re-use the code.

So I landed upon the idea of generating a subref on the fly (from within a Moose-based module) to go from array to tree. Like this (in a simplified way):

use Data::Dump qw/dump/;

sub create_tree_builder {
     my $depth = shift;
     return eval join '', 'sub { $_[0]->{$_[', 
                           join(']}->{$_[', (1..$depth)),
                          ']} = $_[',  $depth + 1 , '] }'; 
}


my $s = create_tree_builder(5);
my $tree = {};

$s->($tree, qw/one two three four five/, 'a value');

print dump $tree;

# prints
#  {
#     one => { two => { three => { four => { five => "a value" } } } },
#  }

This opened up worlds to me, and I'm finding cool uses for this process of eval-in a parametrically generated string into a function all over the place (clearly, a solution in search of problems).

However, it feels a little too good to be true, almost.

Any advice against this practice? Or suggestion for improvements?

I can see clearly that eval-ing arbitrary input might not be the safest thing, but what else?

Follow up

Thanks for all the answers. I used amon's code and benchmarked a bit, like this:

use Benchmark qw(:all) ;

$\ = "\n";

sub create_tree_builder {
 my $depth = shift;
 return eval join '', 'sub { $_[0]->{$_[', 
               join(']}->{$_[', (1..$depth)),
              ']} = $_[',  $depth + 1 , '] }'; 
}


my $s = create_tree_builder(5);

$t = sub {
$_[0] //= {};

    my ($tree, @keys) = @_;
    my $value = pop @keys;

    $tree = $tree->{shift @keys} //= {} while @keys > 1;
    $tree->{$keys[0]} = $value;
};


cmpthese(900000, {
        'eval'  => sub { $s->($tree, qw/one two three four five/, 'a value') },
    'build' => sub { $t->($tree, qw/one two three four five/, 'a value') },

});

The results are clearly in favour of building the tree, not of the eval'ed factory:

            Rate build  eval
build  326087/s    --  -79%
eval  1525424/s  368%    -- 

I'll admit I could have done that before. I'll try with more random trees (rather than assigning the same element over and over) but I see no reason that the results should be different.

Thanks a lot for the help.


Solution

  • It is very easy to write a generalized subroutine to build such a nested hash. It is much simpler that way than writing a factory that will produce such a subroutine for a specific number of hash levels.

    use strict;
    use warnings;
    
    sub tree_assign {
    
      # Create an empty tree if one was not given, using an alias to the original argument
      $_[0] //= {};
    
      my ($tree, @keys) = @_;
      my $value = pop @keys;
    
      $tree = $tree->{shift @keys} //= {} while @keys > 1;
      $tree->{$keys[0]} = $value;
    }
    
    tree_assign(my $tree, qw/one two three four five/, 'a value');
    
    use Data::Dump;
    dd $tree;
    

    output

    {
      one => { two => { three => { four => { five => "a value" } } } },
    }