1

Background

In the code I'm writing, I'm passing data into methods using a hash-ref (see note [1]).

This, unfortunately, leads to a lot of repetitive code:

sub thing {
  my ($self, $params) = @_;

  my ($foo, $bar, $baz, $biff,);
  if ( exists $params->{foo} && $params->{foo} ) {
     $foo = $params->{foo};
  }
  # repeat for `bar`, `baz`, `biff`

  ## rest of function ##
}

(and duplicate in every function with parameters)

What I want to do

What would be far easier is to define a list of parameters, and then iterate of that list, creating both the variables and setting them to a value if needed.

So to test this, I tried:

my $params = { x => 1, y => 2};
my @params = qw(x y z a b c);

gno strict 'refs';
rep( ${$_}, @params );
use strict 'refs';

foreach my $p (@params) {
  if ( exists $params->{$p} && $params->{$p} ) {
    ${$p} = $params->{$p};
  }
}
print "x:$x, y:$y, z:$z, a:$a, b:$b, c:$c\n"

which gives me the following error:

Global symbol "$x" requires explicit package name at ./test.pl line 20.
Global symbol "$y" requires explicit package name at ./test.pl line 20.
Global symbol "$z" requires explicit package name at ./test.pl line 20.
Global symbol "$c" requires explicit package name at ./test.pl line 20.

Can I do this dynamic variable creation thing? (and if so, how?)


[1] By using a hash to pass data in, I gain in many ways:

  1. There is a clear indication of What each item of data is
  2. The ORDER of the pieces of data is no longer important
  3. I can miss one or more pieces of data, and I don't need to add in random undef values
  4. I'm passing less data: 1 scalar (a reference) rather than multiple scalars
  5. (I accept the danger of functions being able to change the parent's data, rather that mucking around with a copy of it...)
6
  • 1
    Those are warnings, you have not declared variables $x, $y, $z, $c. Commented Dec 1, 2015 at 11:55
  • @serenesat - I know.... those are the variables I'm trying to dynamically create from the @params list Commented Dec 1, 2015 at 12:09
  • 1
    See also How to specify default values for optional subroutine arguments? Commented Dec 1, 2015 at 12:10
  • @HåkonHægland The Validate::Params thing is nice.... but one still needs to create a list of variables, and then iterate over each possible parameter to [possibly] set the scalar value... Commented Dec 1, 2015 at 12:21
  • 4
    perl.plover.com/varvarname.html Commented Dec 1, 2015 at 13:29

5 Answers 5

5

Yes, you can do this in Perl. But it's a terrible idea for all of the reasons explained by Mark Dominus in these three articles.

It's a far better idea to store these values in a hash.

#!/usr/bin/perl

use strict;
use warnings;

my $params = { x => 1, y => 2};
my @params = qw(x y z a b c);

my %var;

foreach my $p (@params) {

  # You need to take care exactly what you want in this
  # logical statement. The options are:
  # 1/ $p exists in the hash
  #    exists $params->{$p}
  # 2/ $p exists in the hash and has a defined value
  #    defined $params->{$p}
  # 3/ $p exists in the hash and has a true value
  #    $params->{$p}
  # I think the first option is most likely. The last one has
  # good chance of introducing subtle bugs.

  if ( exists $params->{$p} ) {
    $var{$p} = $params->{$p};
  }
}

print join ', ', map { "$_: " . ($var{$_} // 'undef') } @params;
print "\n";
Sign up to request clarification or add additional context in comments.

3 Comments

Note that && $params->{$p} will skip any untrue value (zero, empty string etc), which may or may not be what one wants.
Bugger. Yes, I spotted that as I was writing my answer, but forgot to fix it. Updated answer to clarify.
Yah - but if ( exists $params->{$p} && defined $params->{$p} ) will fix that (and, incidental, my own bad code)
2

It's a really bad idea to use symbolic references like this... hashes pretty well completely eliminate the need for this.

use warnings;
use strict;

my $params = { x => 1, y => 2, foo => 3, };

thing($params);

sub thing {
    my $params = shift;
    my $foo;
    if (defined $params->{foo}){
        $foo = $params->{foo};
    }
    print $foo;
}

You can also pass in a hash itself directly (whether it be pre-created, or passed inline to the sub. If pre-created, the sub will operate on a copy).

thing(foo => 1, x => 2);

sub thing {
    my %params = @_;
    print $params{foo} if defined $params{foo};
}

Comments

1

With thanks to Dave Cross & others - the following test works:

#!/usr/bin/perl 
use strict;
use warnings;
use English qw( -no_match_vars ) ;
use Carp;
use Data::Dumper;

my $params = { x => 1, y => 2, z => 0};
my @params = qw(x y z a b c);

my %var;

foreach my $p (@params) {
  if ( exists $params->{$p} ) {
    $var{$p} = $params->{$p};
  } else {
    $var{$p} = undef;
  }
}

print Dumper \%var;

This gives me %var with all desired parameters (as listed in @params, with the ones that are not passed in (ie, not in the $params hashref) created with an undef value.

Thus I can confidently test for value and truth, without worrying about existence.

Thank you all.

Comments

0

I did this using soft references:

#!perl

no strict "refs";

my %vars = ( x => 1, y => 2 );

for my $k ( keys %vars ) {
  $$k = $vars{$k};
}

print $x, $y;

But there's a reason why the recommended settings (use strict; use warnings;) prevent this kind of pattern. It is easy to shoot yourself in the foot with it.

2 Comments

Interesting....... however this leaves me without the existence of the optional parameters - which means I can't even do if exists $foo, let alone if defined $foo (and yes - I appreciate the opportunities for "hole-in-foot" :) )
You can still do if exists $foo. But using the above style of code, $foo can magically spring into life.
0
perl -Mstrict -MData::Dumper -wE'
  {package Data::Dumper;our($Indent,$Sortkeys,$Terse,$Useqq)=(1)x4}
  my @aok = qw( x  y  z  a  b  c );
  my %dft = ( a => -1 );
  say "- - - -";
  my $arg = { x => 1, y => 2, foo => 42 };
  $arg = { %dft, %$arg };
  say "arg: ", Dumper($arg);

  my %var;                     
  @var{ @aok } = @$arg{ @aok };
  say "var: ", Dumper(\%var);

  my %aok = map { $_ => 1 } @aok;
  my @huh = grep !$aok{$_}, sort keys %$arg;
  @huh and say "huh: ", Dumper(\@huh);
'
- - - -
arg: {
  "a" => -1,
  "foo" => 42,
  "x" => 1,
  "y" => 2
}

var: {
  "a" => -1,
  "b" => undef,
  "c" => undef,
  "x" => 1,
  "y" => 2,
  "z" => undef
}

huh: [
  "foo"
]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.