user110084 user110084 - 1 month ago 8
Perl Question

Perl: passing two hashes to sub

Below is a small sub to calculate distance between two points by passing two hashes each containing the x and y coordinates. I get a "syntax error near ]{" fatal error at the line calling the sub. Just started on Perl yesterday, not entirely sure what I am doing. How do I pass two hashes to a sub to return a value? Tried this without much success and not sure what I need to work on (hope it is ok to refer to an outside link).

%dot1 = ('x'=>5, 'y'=>6);
%dot2 = ('x'=>7, 'y'=>8);

sub dist {
my (%hash1) = @_[0];
my (%hash2) = @_[1];

$dist = ((@_[0]{'x'}-@_[1]{'x'})**2 + (@_[0]{'y'}-@_[1]{'y'})**2)**0.5;
}


$D = dist(\%dot1,\%dot2);

Answer

First and foremost, you should start every file with

use strict;
use warnings;

This lets Perl catch the most obvious errors in code.


This part is mostly fine, but under use strict Perl will complain about %dot1 and %dot2 being undeclared (and without strict they will be implicitly global, which is usually not what you want):

%dot1 = ('x'=>5, 'y'=>6);
%dot2 = ('x'=>7, 'y'=>8);

Change it to

my %dot1 = ('x'=>5, 'y'=>6);
my %dot2 = ('x'=>7, 'y'=>8);

The call

$D = dist(\%dot1,\%dot2);

has the same problem: It should be

my $D = dist(\%dot1,\%dot2);

What it does is pass references to %dot1 and %dot2 to the sub dist.


    my (%hash1) = @_[0];

This line doesn't make much sense: @_[0] is a list slice, returning a list of elements of @_ corresponding to the indices 0. In other words, it's a one-element slice and better written as $_[0], accessing the single element directly.

But in either case it doesn't make sense to assign a single element to a hash. Perl will interpret it as a key and set the corresponding value to undef. Your call passed \%dot1 as the first argument, so $_[0] is a reference to a hash. By using it as a hash key, Perl will convert it to a string, yielding something like "HASH(0x0075ADD40)".

Your choices at this point are to either dereference the reference right there and make a copy:

    my %hash1 = %{ $_[0] };   # effectively performs %hash1 = %dot1

Or keep the reference and dereference it each time you need access to the hash:

    my $hashref1 = $_[0];     # $hashref1->{foo} accesses $dot1{foo} directly

    $dist = ((@_[0]{'x'}-@_[1]{'x'})**2 + (@_[0]{'y'}-@_[1]{'y'})**2)**0.5;

There are a few issues here. First, you don't need the (implicitly global) variable $dist. You just want to return a value from the sub, which can be done with return. Then, as explained above, @_[0] and @_[1] should be $_[0] and $_[1], respectively. Fixing that we get

    return (($_[0]{'x'} - $_[1]{'x'}) ** 2 + ($_[0]{'y'} - $_[1]{'y'}) ** 2) ** 0.5;

This does indeed work ($_[0]{'x'} is syntactic sugar for $_[0]->{'x'}, i.e. this expression dereferences the hash reference stored in $_[0] to reach the 'x' key of %dot1).

But we didn't use the variables we just created at all. Depending on which way you want to go, you should replace $_[0]{foo} by either $hash1{foo} or $hashref1->{foo} (and similar for $_[1] and %hash2/$hashref2).

Finally, instead of ** 0.5 we can just use sqrt.


Here's how I'd write it:

use strict;
use warnings;

sub dist {
    my ($p1, $p2) = @_;
    return sqrt(($p1->{x} - $p2->{x}) ** 2 + ($p1->{y} - $p2->{y}) ** 2);
}

my %dot1 = (x => 5, y => 6);
my %dot2 = (x => 7, y => 8);

my $D = dist(\%dot1, \%dot2);

print "Result: $D\n";
Comments