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

I'm working on a package, hopefully done by this weekend, where the user's data is stored internal, and there are several 'hooks' for which the user can modify this data. E.g., the code from the user end looks something like:
my $package = My::Package->new( \&user_sub ); $package->add_data( @user_data ); my $value = $package->operate_on_all_data(); sub user_sub { # code here }

The user's code will need to have predefined variables that will work on each object in turn. If the only thing they were allowed access to was the data itself, then I would use $_ as below:

# in My::Package sub operater_on_all_data { my $value; foreach ( @internal_data_storage ) { $value += &user_sub_variable(); } } # in user_sub sub user_sub { return $_ * 2; }
But I have a case where I want to supply not only the user the data to work with, but some current state information that is not accessible from the class interface (for good reason) that would change with each data that is processed. The question becomes, how to you approach this problem ?

There are two readily obvious solutions: First, one could use package-level globals (aka Helpers) that could be referenced, e.g.:

# in My::Package sub operater_on_all_data { my $value; my $i = 0; foreach ( @internal_data_storage ) { $helper = $i++; $value += &user_sub_variable(); } } # in user_sub sub user_sub { return $_ * 2 * $My::Package::helper; }
This is the method that File::Find uses; $_ is the file name, while $File::Find::dir is the current directory, plus some other helpers. This is nice and extendable if more helper variables are needed, but it can make the user's code look messy.

The other alternative is to pass arguements to the user's sub:

# in My::Package sub operater_on_all_data { my $value; my $i = 0; foreach ( @internal_data_storage ) { $helper = $i++; $value += &user_sub_variable( $i++ ); } } # in user_sub sub user_sub { my $multiplier = shift; return $_ * 2 * $multiplier; }
And while this cleans up the code for the user drastically, it forces an 'order' on the helper variables. In addition, if new helper variables are added, the order of the sub might change and break existing user code. There are some ways of fixing this, such as simply giving the user a hash with predefined keys, but then this starts making the code ugly again.

As a side problem, I'm also considering the case when I need to pass two data items to the user sub. sort nicely gives you $a and $b to work with, but that's a perl internal. In addition, $_ would be a bad way to approach it, since which of the two would you set $_ to, and where do you set the other data? So if I went the way of package-level helper variables, this would still work but increase the ugliness of the user's code, while going with passing arguements would make it simple for the user but again suffers from the problems listed above.

Any suggestions on which way is considered better? Or is there another way that I'm missing?


Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain

Replies are listed 'Best First'.
Re: Passing package-level/helper variables to user coderefs
by jeroenes (Priest) on May 18, 2001 at 18:39 UTC
    What I've learned from fellow monks and from reading 'Code complete' from McConnell, it appears to be the best to make your interfaces as "independent" as possible. So in general, only use the interface (@_) to pass data between your package and the user-sub. Globals could be messed with by other subs, and so mess up your code. Moreover, they are harder to maintain in the long run.

    The cleanest way to go is indeed the hashkeys way. Several advantages: Clean namespace, extendible/ flexible, not dependent on the order of args. You say it uglifies the user code. Well, that may be true (qw/beaty eyes beholder/), but it is very readible:

    sub user_sub( my %input = @_; $input{numberX} * $input{multiplier} + $user_glob; }
    This is a bit more writing than your examples maybe, but very clear.

    And I think you summed up the pro's/contra's pretty well. Try to think more like: "is it clean/functional code?" than "is the code very nicely looking?". At least, that would be my approach.

    Jeroen
    "We are not alone"(FZ)
    Fixed error

      or you could use "named params"
      sub user_sub( my %input = @_; $input{numberX} * $input{multiplier} + $user_glob; }