2

I am declaring a hash and at the time of its declaration I am using it inside for one of its element as input to other element. You may grasp easily by following code which is not compiling as Strict pragma is ON:

my %cob = (
        'a' => 0,
        'b' => 0,
        'z' => sub {
                    my ($a, $b) = ($cob{'a'}, $cob{'b'});
                    return ($a+$b+1);
                }                                       
    );

And its producing compile time error.

So my question is how can I reuse the same hash elements as input to other elements of same hash at the time of declaration? Here element 'a' and 'b' are inputs to 'z' element function.

Logically if the hash has not been declared then it cant be used so so how to use one element as input to other element of same hash at the time of declaration? Hopefully I am clear...

P_Z
  • 199
  • 2
  • 8

2 Answers2

4

Consider creating shared $aa and $bb variables inside a lexical closure to generate new cob hashes.

sub make_cob {
  my($aa,$bb) = (0, 0);
  { a => \$aa,
    b => \$bb,
    z => sub { $aa + $bb + 1 },
  };
}

The variable names $aa and $bb avoid a warning in the perlvar documentation on $a and $b in case you ever need to perform any sorting in make_cob:

  • $a
  • $b
    Special package variables when using sort. Because of this specialness $a and $b don’t need to be declared (using use vars, or our) even when using the strict 'vars' pragma. Don’t lexicalize them with my $a or my $b if you want to be able to use them in the sort comparison block or function.

Using one as a plain hash %cob looks like

my %cob = %{ make_cob() };
${$cob{a}} = 10;
${$cob{b}} = 20;
print "z: ", $cob{z}(), "\n";

As a hash reference $cob, the code is

my $cob = make_cob;
${$cob->{a}} = 30;
${$cob->{b}} = 40;
print "z: ", $cob->{z}(), "\n";

You might wrap them all in anonymous subs, as in

sub make_cob {
  my($aa,$bb) = (0, 0);
  { a => sub { if (@_) { $aa = shift } else { $aa } },
    b => sub { if (@_) { $bb = shift } else { $bb } },
    z => sub { $aa + $bb + 1 },
  };
}

my $cob = make_cob;
$cob->{a}(40);
$cob->{b}(50);
print "a: ", $cob->{a}(), "\n",
      "b: ", $cob->{b}(), "\n",
      "z: ", $cob->{z}(), "\n";

But if you are going to all that trouble, make your cobs instances of a Cob class.

package Cob;

use strict;
use warnings;

sub new {
  my($class,$aa,$bb) = @_;
  $_ = defined $_ ? $_ : 0 for $aa, $bb;
  bless { a => $aa, b => $bb } => $class;
}

sub a { $_[0]->{a} }
sub b { $_[0]->{b} }
sub z { $_[0]->a + $_[0]->b + 1 }

1;

Exercise this class with

#! /usr/bin/env perl

use strict;
use warnings;

use Cob;

my $cob = Cob->new(1,2);
print "a: ", $cob->a, "\n",
      "b: ", $cob->b, "\n",
      "z: ", $cob->z, "\n";

Output:

a: 1
b: 2
z: 4
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
  • Thanks for the input. The class really transformed the code. But I was somewhat anxious to use this feature of Perl that the function can be called inside a hash key value. Actually I liked this so much and kind of biased :). As suggested above. it has memory leaks so your class definition is an approach what one should ad.opt – P_Z Apr 18 '19 at 12:18
  • @BabarKamran You're welcome! I'm glad the suggestion helped. – Greg Bacon Apr 18 '19 at 19:53
2
my ($a, $b) = ($cob{'a'}, $cob{'b'});

In order for Perl to compile this statement, %cob must be declared somewhere prior to the statement, but %cob has not yet been declared (as it is part of the statement). The solution is to declare %cob before the statement:

my %cob;   # declare the variable first
%cob = (
        'a' => 0,
        'b' => 0,
        'z' => sub {
                    my ($a, $b) = ($cob{'a'}, $cob{'b'});  # now %cob is known to be a hash
                    return ($a+$b+1);
                }                                       
    );
Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174
  • 2
    It's important to note that your solution creates a memory leak. `%cob` will never be freed unless you take steps to explicitly do so. This is particularly bad if `%cob` contains objects with destructors as they may or may not be call during global destruction. – ikegami Apr 13 '19 at 23:15
  • 1
    @ikegami Thanks for the information. I will look into this and come back when I understand more. I guess one would need to use `Scalar::Util::weaken` somehow? – Håkon Hægland Apr 14 '19 at 08:22
  • 2
    I don't see how. The first reference to the hash is owned by the `%cob` in the scope that contains the snippet, and the second reference to the hash is owned by the `%cob` in the sub. Neither of those are Perl references (the type of value), so there's nothing to weaken. – ikegami Apr 14 '19 at 11:46
  • @ikegami; Well I thought that the hash object will get destroyed automatically. thanks for the input. Thats why my machine OS applications showed abnormal behavior. – P_Z Apr 18 '19 at 07:08
  • 1
    It normally would, except a circular reference is created just if you had done `my %h; $h{x} = \%h;`, and that prevents it from getting destroyed before the program ends. – ikegami Apr 18 '19 at 07:14