Herdsman
Herdsman

Reputation: 879

Perl: Hash slices cannot be lexically scoped

I have really no idea, why this is wrong:

#!/usr/bin/perl
use v5.20;
package MyDate;
sub new{ bless {}, shift; }
sub AUTOLOAD{
    my $f = our $AUTOLOAD;
    my @h{qw[Wday Month Year]} = (localtime)[3,4,5];
}

Err:compilation error near "@h{"


If I delete my (or even if package-scoped with our):

@h{qw[Wday Month Year]} = (localtime)[3,4,5];

It will magically works. Why cannot be hash slices lexically scoped?

Edit: Yes - I have not noticed, that (localtime)[3] = mday not wday. But that is not the point. I am asking about the scope, not localtime func.

Edit2: The hash %h (the point of my question), is intended to be used inside the autoload sub (well, of course when I am trying to use it as hash slice there). Just for clarity.

Upvotes: 2

Views: 172

Answers (3)

daotoad
daotoad

Reputation: 27193

This is just a big WAG at what the OP is trying to do. So it's a pretty crappy SO answer according to site conventions, but I think it might help unmuddy the waters for our suffering OP.

I'm going to start with the code the OP posted, comment a bit on it, and then move to several examples of the "right way to do it".

OP's code:

#!/usr/bin/perl

So we're running a script.

use v5.20;

With version 5.20 or better. So far, so good.

package MyDate;

Now we've selected a new namespace/package called MyDate. While this isn't illegal, it is generally considered desirable to have one package per file.

sub new{ bless {}, shift; }

We have a constructor. So MyDate is going to be a class. Maybe worth looking at Moose or Moo for help with automating some of the boring crap with class construction. But there isn't anything wrong with using good, ole' classical Perl objects.

sub AUTOLOAD{
    my $f = our $AUTOLOAD;
    my @h{qw[Wday Month Year]} = (localtime)[3,4,5];
}

The syntax error, the source of all pain. AUTOLOAD is called to handle any unknown function calls in the namespace. So, this in-effect, intercepts all undefined method calls. MyDate has an infinite list of methods. It's probably not what is really needed.

Let's rework things a bit:

Here's my guess at the sort of thing the OP may want in their script file:

#!/usr/bin/perl
use v5.20;
use strict;    # Make life easier by catching bugs at compile time.
use warnings;  # Catch things that probably indicate errors, but aren't technically illegal.

use MyDate;    # Load my date class

# Make some dates
my $today = MyDate->new();
my $aprilish = MyDate->new( month => 4 );

# Do stuff with them!
print_date( $today );
print_date( $aprilish );

sub print_date {
    my ($date) = @_;

    say "Weekday: ", $date->wday();
    say "Month: ", $date->month();
    say "Year: ", $date->year();
}

Library File: MyDate.pm

package MyDate;

use v5.20;     # Set the minimum perl version required. Optional, but useful.
use strict;    # Always
use warnings;

sub new {
    my ($class, %parts) = @_;

    my %defaults; @defaults[wday month year] = localtime(3, 4, 5)
    my $self = bless {
        %defaults,
    }, $class;

    for my $part ( qw/ month wday year /) {
        next unless exists $parts{$part};

        $self->$part( $parts{$part} );  # Call the associated method to initialize an attribute.
        delete $parts{$part};
    }

    die "Unknown attributes: ", join ', ', keys %parts  # Fatal error on unknown args
        if %parts;

    return $self;
}

# The other methods are mostly identical.
sub month {
    my ($self, $value) = @_;

    if ( @_ == 2 ) {  # If two args are passed, we are a setter.
        $self->{month} = $value;
    }

    return $self->{month};
}

That's a classical perl OO version of something like the OP is going for, I think.

It's a lot less hassle to write with Moo.

package MyDate;

use v5.20;     # Set the minimum perl version required. Optional, but useful.
use Moo;       # Turns on strict and warnings;
use namespace::autoclean;  # Removes any utility functions so they don't show up as methods.    

has 'month' => (
  is => 'ro',
  builder => 1,
);

has 'wday' => (
  is => 'ro',
  builder => 1,
);

has 'year' => (
  is => 'ro',
  builder => 1,
);

sub _build_month { localtime()[4] }
sub _build_wday { localtime()[3] }
sub _build_year { localtime()[5] }

But probably the best thing to do would be to take an existing date manipulation library like DateTime and use it.

#!/usr/bin/perl
use v5.20;
use strict;    # Make life easier by catching bugs at compile time.
use warnings;  # Catch things that probably indicate errors, but aren't technically illegal.

use DateTime;    # Load my date class

# Make some dates
my $today = DateTime->today();
my $aprilish = DateTime->today()->set_month( 4 );

# Do stuff with them!
print_date( $today );
print_date( $aprilish );

sub print_date {
    my ($date) = @_;

    say "Weekday: ", $date->day_of_week();
    say "Month: ", $date->month();
    say "Year: ", $date->year();
}

Anyhow, I hope that all this is useful to the OP, and maybe, just maybe, to someone else.

Upvotes: 0

ikegami
ikegami

Reputation: 386541

@h{...} is not a variable, so you can't declare it as such.

@h{...} = ...; sets elements of %h. So it's %h you need to create.

This is done as follows:

my %h;

By the way, I doubt you have a legitimate reason for using AUTOLOAD. Keep in mind that code at the top level (at the file level) of a module will be executed when the module is first loaded in an interpreter.

Upvotes: 2

Polar Bear
Polar Bear

Reputation: 6808

I hope that you will see your mistakes from following piece of code

use strict;
use warnings;
use diagnostics;

use v5.20;

package MyDate;

sub new{ bless {}, shift; }

sub AUTOLOAD{
    my $f = our $AUTOLOAD;
    #my %h;               # !!! without hash declaration compilation error
    #     0    1    2     3     4    5     6     7     8
    #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
    #                                            localtime(time);
    @h{qw[Wday Month Year]} = (localtime)[6,4,5];
}

perl hash_package.pl

Global symbol "%h" requires explicit package name (did you forget to declare "my %h"?) at hash_package.pl line 15.
Execution of hash_package.pl aborted due to compilation errors (#1)
    (F) You've said "use strict" or "use strict vars", which indicates
    that all variables must either be lexically scoped (using "my" or "state"),
    declared beforehand using "our", or explicitly qualified to say
    which package the global variable is in (using "::").

Uncaught exception from user code:
        Global symbol "%h" requires explicit package name (did you forget to declare "my %h"?) at hash_package.pl line 15.
        Execution of hash_package.pl aborted due to compilation errors.

Upvotes: 1

Related Questions