djthoms
djthoms

Reputation: 3106

Loop through an anonymous array inside perl hash

Background: I am new to Perl and I am tinkering with a simple script that loops through a hash value that is an anonymous array.

The Issue I cannot seem to loop through the array. All I get is ARRAY(0x1663b78)

The Code

#!/usr/bin/perl
package Foo;
use strict "vars";

sub new {
    my $class = shift;
    my $self = {
        distro  => "",
        pkg_mgr => "",
        options => ["PHP + Apache", "PHP + Lighthttpd", "PHP + Nginx", "RubyGems + Rails", "Node JS + NPM"]
    };

    bless $self, $class;

    return $self;
}

sub print_options {
    my($self) = @_;
    foreach($self->{options}) {
        print $_ . "\n";
    }
}

my $setup = new Foo();
$setup->print_options();

Also, if someone could kindly inform me if it's remotely useful to use use strict "vars"; if I really have no need for private or public variables. I know how I would do this in PHP but I can't wrap my head around this in Perl.

Lastly, my reason for using Perl is because eventually I'm going to make an application that installs software for servers (including PHP). This script will require user interaction via command line.

Upvotes: 3

Views: 2606

Answers (3)

David W.
David W.

Reputation: 107080

A few little Perl clues:

  • Use Data::Dumper whenever you have problems like this. Data::Dumper will help you understand the structure.
  • If you're going to go all object oriented, go full bore. You have constructors and methods. Methods should return values, and you operate on those values. Each method and constructor should be as independent as possible and have little knowledge of your overall object structure.
  • Operate in the main package. You should flip your program around. The last two statements should be on the top, and define your package below.

If you used Data::Dumper and dumped the data of $self when you called it, you would realize that $self is a hash with three keys, and that the options key is pointing to an anonymous array.

In order to do what you want, you have to dereference that anonymous array:

sub print_options {
    my($self) = @_;
    for ( @{$self->{options} } ) {
        print $_ . "\n";
    }
}

However, let's clean up your program to make it a complete class. First, methods never print -- they return values. Thus, you should not have a print_options method. Instead, it should return the array (or a reference to an array).

Also, your constructor doesn't have to know how your Options are structured. What if you change it? Let's look at a changed program:

#! /usr/bin/env perl
#
use warnings;
use strict;
use feature qw(say);

my $setup = Foo->new;
for my $option ( $setup->Option ) {
    say $option;
}

package Foo;

sub new {
    my $class = shift;
    my $self = {};
    bless $self, $class;
    $self->Option("PHP + Apache");
    $self->Option("PHP + Lighthttpd");
    $self->Option("PHP + Nginx");
    $self->Option("RubyGems + Rails");
    $self->Option("Node JS + NPM");
    return $self;
}

sub Distro {
    my $self   = shift;
    my $distro = shift;
    if ( defined $distro ) {
        $self->{DISTRO} = $distro;
    }
    return $self->{DISTRO};
}

sub Package_Manager {
    my $self    = shift;
    my $package = shift;
    if ( defined $package ) {
        $self->{PACKAGE} = $package;
    }
    return $self->{PACKAGE};
}

sub Option {
    my $self = shift;
    my $option = shift;;

    if ( not exists $self->{OPTION} ) {
        $self->{OPTION} = [];
    }

    if ( defined $option ) {
        push @{ $self->{OPTION} }, $option;
    }
    my @array = @{ $self->{OPTION} };
    return wantarray ? @array : \@array;
}

First, unlike PHP, Perl compiles the code before execution. Thus, you can define packages and their subroutine on the bottom of the program. The few lines of code above operation in the default main package. If I did use a package variable in my Foo package, I wouldn't accidentally be using it in my program.

Notice that none of my methods or my constructor know about the structure of the Foo object. When I set my options in my new constructor, I call my Option method to do the setting. This way, if I change the way I store my options, I only have to change my Option method and not worry where else in my package it has to be changed. It also simplifies my program and makes it easier to support.

By making my Option method to either set or get my options, I simplify the code. I can see that I'm storing options in an anonymous array, and it's obvious that I need to dereference it when I return the object. I like using wantarray and giving the user the option whether they want an array or a reference to an array.

Notice that I don't simply return $self->{OPTION}. It is a reference to an array and should have worked, but if I return that, I'm returning the memory location of that object! Thus, if a user changes that reference, they're changing my object without going through my methods. Thus, I make another array, and return a reference to that. Muck with that array reference all you want, you're not changing my object.

This code isn't complete. I can set options, but not unset options. It may be nice to have a way to push and pop options. Instead, all you can do at this point is return the entire list, and not modify it.

Upvotes: 9

stevenl
stevenl

Reputation: 6798

$self->{options} is not an array but a reference to an array. You need to de-reference it to be able to iterate through its values.

my @options = @{ $self=>{options} };

Upvotes: 4

Karthik T
Karthik T

Reputation: 31962

You need to do the below change.

foreach(@{$self->{options}}) {
        ^^                ^

options is the key to an arrayref and you need to de-reference it to use it.

As to your other question, a more general pair of pragmas are recommended, especially for beginners

use strict;
use warnings;

Upvotes: 1

Related Questions