1
votes

i declared the following sub (In reality, the values come out of the Database - so i simplified it):

sub get_date {
 my ($ref_last)=@_;
 $$ref_last->{duration}='24,0,4';
 ($$ref_last->{duration}->{d},
  $$ref_last->{duration}->{h},
  $$ref_last->{duration}->{m})
   = split(/\,/, $$ref_last->{duration});
}

This sub is called from the main-Part of the script, like this:

my $hashy;
get_date(\$hashy);
print $hashy->{duration}->{d};

Everything ist fine, and works like a charm, until i use strict:

use strict;
my $hashy;
get_date(\$hashy);
print $hashy->{duration}->{d};

in this case perl says "Can't use string ("24,0,4") as a HASH ref while "strict refs" in use"

I already tried ref($ref_last) - but ref is a read-only function.

Any suggestions, why this happens - and perhaps a better solution ?

Here's the full (non)-Working script:

#!/usr/bin/perl -w
use strict;
my $hashy;
get_date(\$hashy);
print $hashy->{duration}->{d};

sub get_date {
        my ($ref_last)=@_;
        $$ref_last->{duration}='24,0,4';
        ($$ref_last->{duration}->{d},
         $$ref_last->{duration}->{h},
         $$ref_last->{duration}->{m})
                = split(/\,/, $$ref_last->{duration});
}
3

3 Answers

2
votes

Inside get_date, you assign a string to $ref_last->{duration} but then attempt to access it like a hashref. You also have extra dollar signs that attempt to dereference individual values plucked from the hash.

I would write it as

sub get_date {
  my($ref_last) = @_;
  my $duration = '24,0,4';
  @{ $ref_last->{duration} }{qw/ d h m /} = split /\,/, $duration;
}

The last line is a hash slice that allows you to assign values to the d, h, and m keys in a single list-assignment.

In the context of the caller, you need to set up a bit of scaffolding.

my $hashy = {};
get_date($hashy);

Without initializing $hashy to contain a new empty hashref, get_date does all its assignments and then throws away newly-built edifice. This is because when you copy parameters out of @_, you are using pass-by-value semantics.

Perl will accommodate pass-by-reference as well. Perl has a feature known as autovivification where the language builds necessary scaffolding for you on demand. To use that style, you would write

my $hashy;
get_date($hashy);

sub get_date {
  my($ref_last) = @_;
  my $duration = '24,0,4';
  @{ $_[0]->{duration} }{qw/ d h m /} = split(/\,/, $duration);
}

Note the use of $_[0] to directly access the first parameter, which is an alias to $hashy in this case. That is, get_date modifies $hashy directly.

Either way, say we print the contents with

print "[", join("][" => %{ $hashy->{duration} }), "]\n";

in which case the output is some permutation of

[h][0][m][4][d][24]

Building complex data structures with Perl isn’t difficult, but you have to learn the rules.

2
votes

Based on comments, you're trying to change the format of an existing hash value (from «24,0,4» to «{ d=>24, h=>0, m=>4 }»). Here's how I'd do it.

sub split_duration {  # Changes in-place.
    my ($duration) = @_;
    my %split;
    @split{qw( d h m )} = split(/,/, $duration);
    $_[0] = \%split;
}

my $row = $sth->fetchrow_hashref();
split_duration( $row->{duration} );

or

sub split_duration {
    my ($duration) = @_;
    my %split;
    @split{qw( d h m )} = split(/,/, $duration);
    return \%split;
}

my $row = $sth->fetchrow_hashref();
$row->{duration} = split_duration( $row->{duration} );

Explanation of the problem and initial solutions below.


Without strict, 24,0,4 was treated as a hash reference, which means Perl was creating a variable named $24,0,4!!! That's bad, which is why use strict 'refs'; prevents it.

The underlying problem is your attempt to assign two values to $$ref_last->{duration}: a string

'24,0,4'

and a reference to a hash

 { d => 24, h => 0, m => 4 }

It can't hold both. You need to rearrange your data.

I suspect you don't actually use 24,0,4 after you split it, so you could fix the code as follows:

sub get_date {
    my ($ref_last)=@_;
    my $duration = '24,0,4';
    @{ $$ref_last->{duration} }{qw( d h m )} =
        split(/,/, $duration);
}

If you need 24,0,4, you can reconstruct it. Or maybe, you can store the combined duration along with d,h,m.

sub get_date {
    my ($ref_last)=@_;
    my $duration = '24,0,4';
    $$ref_last->{duration}{full} = $duration;
    @{ $$ref_last->{duration} }{qw( d h m )} =
        split(/,/, $duration);
}

Or in a separate elements of the higher up hash.

sub get_date {
    my ($ref_last)=@_;
    my $duration = '24,0,4';
    $$ref_last->{full_duration} = $duration;
    @{ $$ref_last->{duration} }{qw( d h m )} =
        split(/,/, $duration);
}
0
votes

This happens because you have a weird syntax for your hash reference.

#!/usr/bin/perl -w
use strict;

my $hashref = {};
get_date($hashref);

print $hashref->{duration}->{d};

sub get_date {
    my ($ref_last) = @_;
    $tmp = '24,0,4';

    ($ref_last->{duration}->{d},
     $ref_last->{duration}->{h},
     $ref_last->{duration}->{m})
            = split(/,/, $tmp);
}

and in your subroutine use $ref_last->{duration}, without $$.