0

I am trying to pass an inner array to a function in Perl. Here is my Perl program.

#!/usr/bin/perl
use strict;
use warnings;

my %data = (
    'a' => (
        x => 'Hello',
        y => 'World'
    ),
    'b' => (
        x => 'Foo',
        y => 'Bar'
    )
);

#prototype
sub p(\%);

{ #main
    p(%data{'a'}); # should print "Hello Wolrd".
}

sub p(\%) {
    print "$_[0]{x} $_[0]{y}\n";
}

Instead, I get the following error: Type of arg 1 to main::p must be hash (not key/value hash slice).

This works:

#!/usr/bin/perl
use strict;
use warnings;

#prototype
sub p(\%);

{ #main
    my %a = (
        x => 'Hello',
        y => 'World'
    );
    p(%a);
}

sub p(\%) {
    print "$_[0]{x} $_[0]{y}\n";
}

So there must be something wrong with the method call. But what? The content of a is a hash, so the first character after p( must be % (I tried p($data{'a'}); too, but it leaves me with another error (which seems logical, since the content of a isn’t a scalar). I do not have to manually create a reference to the hash and dereference because I declare the function prototype. What am I missing?

Matthias Ronge
  • 9,403
  • 7
  • 47
  • 63

2 Answers2

4

Your definition of the structure is wrong. Inner hashes need to use {}, not ().

my %data = (
    a => {
        x => 'Hello',
        y => 'World'
    },
    b => {
        x => 'Foo',
        y => 'Bar'
    }
);

Also, to get a single hash element, use $data{'a'} (or even $data{a}), not %data{'a'}.

Moreover, see Why are Perl 5's function prototypes bad? on why not to use prototypes. After correcting the syntax as above, the code works even without the prototype. If you really need the prototype, use %, not \%. But you clearly don't know exactly what purpose prototypes serve, so don't use them.

choroba
  • 231,213
  • 25
  • 204
  • 289
  • Using braces on the inner array definition does not change anything, the error remains. If I access the element using `$data{'a'}`, I get the other error message, which is `Type of arg 1 to main::p must be hash (not hash element)`. Removing the prototype declaration actually makes it work, but prints an ungly warning message: `main::p() called too early to check prototype at ex2.pl line 17.` I do not want to remove the `use warnings;` as many of them seem helfpul to me. Is it also possible to fix this without removing the prototype, **or** to keep warnings enabled without this specific one? – Matthias Ronge Nov 15 '17 at 12:33
  • 1
    Remove both mentions of the prototype. – choroba Nov 15 '17 at 12:38
3

There are no arrays in your code. And there are no method calls in your code.

Your hash is defined incorrectly. You cannot embed hashes inside other hashes. You need to use hash references. Like this:

my %data = (
    'a' => {
        x => 'Hello',
        y => 'World'
    },
    'b' => {
        x => 'Foo',
        y => 'Bar'
    }
);

Note, I'm using { ... } to define your inner hashes, not ( ... ).

That still gives us an error though.

Type of arg 1 to main::p must be hash (not hash element) at passhash line 20, near "})"

If that's unclear, we can always try adding use diagnostics to get more details of the error:

(F) This function requires the argument in that position to be of a certain type. Arrays must be @NAME or @{EXPR}. Hashes must be %NAME or %{EXPR}. No implicit dereferencing is allowed--use the {EXPR} forms as an explicit dereference. See perlref.

Parameter type definitions come from prototypes. Your prototype is \%. People often think that means a hash reference. It doesn't. It means, "give me a real hash in this position and I'll take a reference to it and pass that reference to the subroutine".

(See, this is why people say that prototypes shouldn't be used in Perl - they often don't do what you think they do.)

You're not passing a hash. You're passing a hash reference. You can fix it by dereferencing the hash in the subroutine call.

p(%{$data{a}});

But that's a really silly idea. Take a hash reference and turn it into a hash, so that Perl can take its reference to pass it into a subroutine.

What you really want to do is to change the prototype to just $ so the subroutine accepts a hash reference. You can then check that you have a hash reference using ref.

But that's still overkill. People advise against using Perl prototypes for very good reasons. Just remove it.

Dave Cross
  • 68,119
  • 3
  • 51
  • 97