http://www.perlmonks.org?node_id=1051609

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

In my programs I always end up with a zillion global variables. These are used at different subroutines and it would be very laborious to pass them around as parameters. Any suggestions on an all-inclusive data structure?
my $alias_line_count = 0; my @alias_label_array = (); my @alias_value_array = (); my @officer_label_array = (); my @officer_value_array = (); my $officer_count = 0; my @ap_label_array = (); my @ap_value_array = (); my $ap_line_count = 0; my $ap_person_count = -1;

Replies are listed 'Best First'.
Re: data structure question
by tobyink (Canon) on Aug 30, 2013 at 12:12 UTC

    Time for you to learn OOP!

    { package Person; use Moo; has name => (is => 'ro', required => 1); has age => (is => 'rw', required => 1); sub introduce_yourself { my $self = shift; printf("My name is %s, and I am %d years old.\n", $self->name, + $self->age); } } my $alice = Person->new(name => 'Alice', age => 32); $alice->introduce_yourself;

    Note that we don't need to pass any $name and $age variables to introduce_yourself. It picks them up automatically from the $self object.

    And because we've not used any globals, we can easily have two people living side by side. They each get their own copy of name and age and don't get them mixed up:

    my $alice = Person->new(name => 'Alice', age => 32); my $bob = Person->new(name => 'Bob', age => 31); $_->introduce_yourself for $alice, $bob;

    When two people are living side by side, they don't always remain just two people. Let's extend Person with the ability to breed more people...

    { package Person; use Moo; has name => (is => 'ro', required => 1); has age => (is => 'rw', required => 1); sub introduce_yourself { my $self = shift; printf("My name is %s, and I am %d years old.\n", $self->name, + $self->age); } } { package Breeding; use Moo::Role; has children => (is => 'ro', default => sub { return []; }); sub is_parent { my $self = shift; @{ $self->children }; } sub bear_children { my $self = shift; my ($partner, @kids_names) = @_; for my $name (@kids_names) { my $kid = ref($self)->new(name => $name, age => 0); push @{ $self->children }, $kid; push @{ $partner->children }, $kid; } return; } } { package Person::Breeding; use Moo; extends 'Person'; with 'Breeding'; } my $alice = Person::Breeding->new(name => 'Alice', age => 32); my $bob = Person::Breeding->new(name => 'Bob', age => 31); $_->introduce_yourself for $alice, $bob; $alice->bear_children($bob, 'Carol', 'Dave', 'Eve'); print "Bob is a father!\n" if $bob->is_parent;

    PS: in the interest of pimping my new project Moops, here's that last example again, using Moops instead of plain Moo:

    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
      if $bob->is_parent;

      It's a paternity test.

Re: data structure question
by roboticus (Chancellor) on Aug 30, 2013 at 12:29 UTC

    First I'd suggest using composite objects. It appears that you're not yet using multidimensional arrays/hashes, and I think that's part of the problem. Going by the variable names, I think you could improve things a good deal. First, here's what I'm thinking your variables mean:

    my $alias_line_count = 0; # Number of aliases I have my @alias_label_array = (); my @alias_value_array = (); my @officer_label_array = (); my @officer_value_array = (); my $officer_count = 0; # Number of officers I have my @ap_label_array = (); my @ap_value_array = (); my $ap_line_count = 0; # Number of APs I have my $ap_person_count = -1; # Eh? I don't know...

    If that's the case, then it looks like you're probably using code like this:

    # Print the data for officer 'Joe' for my $i (0 .. $officer_count-1) { if ($officer_label_array[$i] eq 'Joe') { print "Joe: $officer_value_array[$i]\n"; } }

    In perl, you can ask a hash or array how many elements it has, so you don't need to store the size in a separate value. So we could convert it to something like this:

    for my $i (0 .. $#officer_label_array) { if ($officer_label_array[$i] eq 'Joe') { print "Joe: $officer_value_array[$i]\n"; } }

    If you use a multidimensional array, though, you could simplify things. Here, we'll use a second dimension on your array where element 0 is the name, and element 1 is the value:

    for my $i (0 .. $#officer_label_value_array) { if ($officer_label_value_array[$i][0] eq 'Joe') { print "Joe: $officer_label_value_array[$i][1]\n"; } }

    One nice thing about multidimensional arrays like this is that frequently you can solve the problem *without* using the row number. Instead you can just loop through the array:

    for my $array_reference (@officer_label_value_array) { my ($name, $value) = @$array_reference; print "$name: $value\n" if $name eq 'Joe'; }

    Of course, if you're actually just looping through arrays to find things, you'd be better served with a hash, as you can simply access each value by name:

    my $name = 'Joe'; print "$name: $officer_hash{$name}\n" if exists $officer_hash{$name};

    Finally, I don't really see the point of having suffixes like "_array", "_hash", etc., so I think I'd simplify things like:

    my %aliases = { # Name Title Joe => 'Prez', Bob => 'Veep' }; my %officers = { # Name Salary Prez => 100000, Veep => 85000 }; # Who's Joe, and how much money does he make? my $title = $aliases{Joe}; my $salary = $officers{$title}; print "Joe's title is $title, and he earns $salary yearly.\n"; # How many officers do we have, anyway? print "We have ", scalar(keys %officers), " officers.\n";

    Frequently, a few well-named global hashes are fine for simple, small programs. But when your programs grow and you need to start passing things around, it's pretty simple when you use better data structures.

    .... Ack! I'm late for work. I'll try to continue a little later...

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      thanks roboticus! Actually my code (with the exception of generally letting an array or hash tell how big it is), looks pretty similar to what you suggest. I am using multi-dimensional arrays and hashes to good effect. My programs work well; my question was posited on the idea that they don't look very 'Perl-ish'.
Re: data structure question
by daxim (Curate) on Aug 30, 2013 at 11:49 UTC
    Don't. That's the "God object" anti-pattern.

    Keep the variables separate, but move them into the smallest possible scope. That is to say, define them in the function/block where they are needed, not everything at once at the top.

      It does have its uses however.

      In my game, I have a $universe hash, containing all the ships and maps and other info. The ships contain their component layout and stats, and the components contain their shapes and stats.

      When calling functions, I only pass the sub-structure that is needed, but when it comes time to save the game or reload from disk, it only requires a single call to storable or yaml which never has to change. Much simpler than having a growing set of independent things to save.

      There are, of course, still plenty of local variables in small scopes, but anything that has to outlive the program itself goes into my $universe.

        it only requires a single call to storable

        That's the justification for this amazingly sub-optimal design? ಠ_ಠ The hash key names are tight coupling, the location of the "sub-structure" is an implicit contract. That's badly reinventing the facilities of high-level programming Perl/ecosystem provides you with. It's perfectly possible to have a universe object that refers to other objects, but the design would be much more robust against changes.

      thanks, that was an encouraging answer!
Re: data structure question
by hdb (Monsignor) on Aug 30, 2013 at 11:52 UTC

    Use an object-oriented approach. Group your variables into related entities, define them as members of some object and then either define your subroutines as methods of these objects or write subroutines that accept these objects as parameters.

Re: data structure question
by SuicideJunkie (Vicar) on Aug 30, 2013 at 14:17 UTC
    001: my $alias_line_count = 0; my @alias_label_array = (); my @alias_value_array = (); my @officer_label_array = (); my @officer_value_array = (); my $officer_count = 0; my @ap_label_array = (); my @ap_value_array = (); my $ap_line_count = 0; my $ap_person_count = -1;

    Whenever you have arrays that you're trying to keep in synch like that? Try an array of hashes instead.

    my @officers; my %aliases; my $newHire = {value=>0, label=>'Spike Roberts', aliases=>['Bayonet Bob', 'Hey You'] }; push @{$aliases{$_}}, $newHire for @{$newHire->{aliases}}; push @officers, $newHire; print "We now have ". (scalar @officers) . " officers at Fort Futile\n +";
Re: data structure question
by BillKSmith (Monsignor) on Aug 30, 2013 at 14:06 UTC
    Your problem is a symptom of a poorly designed program. Structured and Objected-Oriented designs have the same goal, reduce complexity by reducing data dependecies between components. I may be showing my age, but I believe that the former is easier, except in very large projects. In perl, we can freely mix the concepts when it makes sense. (e.g. You can use DBI in a structured program without knowing, or carring, that the 'handles' are really objects.)
    Bill