views:

2070

answers:

8

I am having a lot of trouble. I have a function that takes a variable and an associative array, but I can't seem to get them to pass right. I think this has something to do with function declarations, however I can't figure out how they work in Perl. Does anyone know a good reference for this and how to accomplish what I need? I should add that it needs to be passed by reference.

sub PrintAA
{
    my $test       = shift;
    my %aa         = shift;
        print $test . "\n";
        foreach (keys %aa)
        {
                print $_ . " : " . $aa{$_} . "\n";
                $aa{$_} = $aa{$_} . "+";
        }
}
+23  A: 

Pass the reference instead of the hash itself. As in

PrintAA("abc", \%fooHash);

sub PrintAA
{
  my $test = shift;
  my $aaRef = shift;

  print $test, "\n";
  foreach (keys %{$aaRef})
  {
    print $_, " : ", $aaRef->{$_}, "\n";
  }
}

See also perlfaq7: How can I pass/return a {Function, FileHandle, Array, Hash, Method, Regex}?

Paul Tomblin
Though not how I thought it needed to be done, this is the easiest method.
rlbond
+4  A: 

Looks like you should pass in a reference to a hash.

sub PrintAA
{
   my $test = shift;
   my $aa = shift;
   if (ref($aa) != "HASH") { die "bad arg!" }
   ....
}

PrintAA($foo, \%bar);

The reason you can't do a

my %aa = shift;

is because perl flattens all the arguments to a subroutine into one list, @_. Every element is copied, so passing in by reference avoids those copies as well.

Matt Kane
+9  A: 

Alternatively:

sub PrintAA
{
    my $test       = shift;
    my %aa         = @_;
        print $test . "\n";
        foreach (keys %aa)
        {
                print $_ . " : " . $aa{$_} . "\n";
                $aa{$_} = $aa{$_} . "+";
        }
}

The thing you're fundamentally missing is that an associative array isn't a single argument (though an associative array reference is, as in Paul Tomblin's answer).

chaos
+3  A: 

As usual there are several ways. Here is what "Perl - Best Practices", that most revered of Style Pointers, has to say about passing parameters to functions:

Use a hash of named arguments for any subroutine that has more than three parameters

But since you have only two, you could get away ;) with passing them directly like this:

my $scalar = 5;
my %hash = (a => 1, b => 2, c => 3);

func($scalar, %hash)

And function is defined like this:

sub func {
    my $scalar_var = shift;
    my %hash_var = @_;

    ... Do something ...
}

I could be more useful if you could show some code

Gurunandan
+8  A: 

This code works:

#!/bin/perl -w

use strict;

sub PrintAA
{
    my($test, %aa) = @_;
    print $test . "\n";
    foreach (keys %aa)
    {
        print $_ . " : " . $aa{$_} . "\n";
    }
}

my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => \&PrintAA );

PrintAA("test", %hash);

The key point is the use of the array context in the my() 'statement' in the function.


What does the array context business actually do?

Succinctly, it makes it work correctly.

It means that the first value in the @_ array of arguments is assigned to $test, and the remaining items are assigned to the hash %aa. Given the way I called it, there is an odd number of items in the @_, so once the first item is assigned to $test, there is an even number of items available to assign to %aa, with the first item of each pair being the key ('aaa', 'bbb', 'ccc' in my example), and the second being the corresponding value.

It would be possible to replace %aa with @aa, in which case, the array would have 6 items in it. It would also be possible to replace %aa with $aa, and in that case, the variable $aa would contain the value 'aaa', and the remaining values in @_ would be ignored by the assignment.

If you omit the parentheses around the variable list, Perl refuses to compile the code. One of the alternative answers showed the notation:

my $test = shift;
my(%aa) = @_;

This is pretty much equivalent to what I wrote; the difference is that after the two my statements, @_ only contains 6 elements in this variation, whereas in the single my version, it still contains 7 elements.

There are definitely other questions in SO about array context.


Actually, I wasn't asking about the my($test, %aa) = @_; I was asking about my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => \&PrintAA ); versus my %hash = { 'aaa' => 1, ... };

The difference is that the { ... } notation generates a hash ref and the ( ... ) notation generates a list, which maps to a hash (as opposed to hash ref). Similarly, [ ... ] generates an array ref and not an array.

Indeed, change the 'main' code so it reads: my(%hash) = { ... }; and you get a run-time (but not compile time) error - treat line numbers with caution since I've added alternative codings to my file:

Reference found where even-sized list expected at xx.pl line 18.
...
Use of uninitialized value in concatenation (.) or string at xx.pl line 13.
Jonathan Leffler
TMTOWTDI, but I prefer this approach.
spoulson
What does the array context business actually do?
Paul Tomblin
Actually, I wasn't asking about the my($test, %aa) = @_;I was asking about my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => \versusmy %hash = { 'aaa' => 1, ... };
Paul Tomblin
Keep in mind that if the array is large, references are much more efficient. This calling style pushes all array elements on the stack, and pops them all off. A reference is a single element, regardless of array size.
Craig Lewis
+3  A: 

All the above methods work, but this was always the way I preferred to do things like this:

sub PrintAA ($\%)
{
    my $test       = shift;
    my %aa         = ${shift()};
    print "$test\n";
    foreach (keys %aa)
    {
        print "$_ : $aa{$_}\n";
        $aa{$_} = "$aa{$_}+";
    }
}

Note: I also changed your code a bit. Perl's double-quoted strings will interpret "$test" to be the value of $test rather than the actual string '$test', so you don't need that many .s.

Also, I was wrong about how the prototypes work. To pass a hash, use this:

PrintAA("test", %hash);

To print a hash reference, use this:

PrintAA("test", %$ref_to_hash);

Of course, now you can't modify the hash referenced by $ref_to_hash because you're sending a copy, but you can modify a raw %hash because you're passing it as a reference.

Chris Lutz
Downvoted because 1) this code doesn't work and 2) Perl prototypes don't do what most people (apparently including you) expect them to. They're good for emulating the behavior of built-in functions but not much use otherwise. The "\%" part of the prototype says that the second argument must be a hash and to pass it by reference. It has to be a *real* hash. It can't be a hashref or a list of key => value pairs.
Michael Carman
Thank you for the corrections. It makes no sense that \% can't be a reference, but it's true. I don't think he'll be passing a list of key => value pairs, as his function seems to be modifying the hash he passes in as a parameter, but the other points are valid.
Chris Lutz
A: 

Use the folowing sub to get hash or hashref - whatever passed :)

sub get_args { ref( $_[0] ) ? shift() : ( @_ % 2 ) ? {} : {@_}; }
sub PrintAA
{
  my $test = shift;
  my $aa = get_args(@_);;
  #then
  $aa->{somearg} #do something
  $aa->{anotherearg} #do something

}

Call your function like this:

printAA($firstarg,somearg=>1, anotherarg=>2)

Or like this(no matter):

printAA($firstarg,{somearg=>1, anotherarg=>2})

Or even like this(no matter):

my(%hash) = ( 'aaa' => 1, 'bbb' => 'balls', 'ccc' => \PrintAA );

PrintAA("test", %hash);

Cheers!

Berov
Using prototypes can let you do the same thing, as well as giving compile-time checking of your subroutine's arguments. Plus, your get_args will be fooled by an array of references.
Chris Lutz
It is for hash(ref) :) - I know it does not work with arrays
Berov
@Chris - prototypes have no effect when a subroutine is invoked as a method, they're awful and confusing and break things. As someone else posted previously, don't use them unless you need to make a sub work like a built-in.
converter42
+1  A: 

Arguments to functions get flattened into a single array (@_). So it's usually easiest to pass hashes to function by reference.

To create a HASH:

my %myhash = ( key1 => "val1", key2 => "val2" );

To create a reference to that HASH:

my $href = \%myhash

To access that hash by reference;

%$href

So in your sub:

my $myhref = shift;

keys %$myhref;
Arnshea