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

Hello, I've stumbled upon an implementation problem in my script. Let's say I have a hash of phrases as keys and values as referenced unonymous hashes of quantities. There are several different types of quantities. For example: qty, qtySum, qtyCont, etc. Different keys may have different qty types. In other words, not all qty types exist in each key:
my %phrases = ( "phrase 1"=>{ qty=>1, qtySum=>5, qtyCont=>8 }, "phrase 2"=>{ qty=>10, qtyCont=>34 }, "phrase 3"=>{qty=>1} );
There are places in the program that need totals of those quantities. So, the first thing that I did, was iterated through an entire hash and added a qtyTotal with the sum of all of the qtys for each phrase to each key's anonymous referenced hash. Then, while writing a program, I've found that each qty value can change, which requires for the qtyTotal to be recalculated, so I descided to create a subroutine that I will use as referenced subroutine in qtyTotal instead of a statically calculated value. That subroutine will calculate all of the qty's dynamically when the qtyTotal is called. At first, it seemed all rainbowy until I found out that the subroutine needs to know which key's qty values need to be calculated:
my %phrases = ( "phrase 1"=>{ qty=>1, qtySum=>5, qtyCont=>8, qtyTotal=>\&qtyTotal }, "phrase 2"=>{ qty=>10, qtyCont=>34, qtyTotal=>\&qtyTotal }, "phrase 3"=>{ qty=>1, qtyTotal=>\&qtyTotal } ); sub qtyTotal{ my $qty = 0; my $key = #I don't know what to use my $phObj = $phrases{$key}; foreach my $qType ('qty', 'qtySum', 'qtyCont'){ $qty += $phrases{$key}->{$qType} if exists $phrases{$key}->{$qType}; } return $qty; }
So, the question, as I mentioned above and as you saw in the sub, how does the subroutine know it's container hash key, in order to calculate the right key's qty values? I want to be able to get calculated result when I say something like:
print "$phrases{'phrase 2'}->{'qtyTotal'}\n";
In this example's case, the result should be: 44 , since the subroutine knew that it was called from the container with 'phrase 2' key.

Thanx 4 all help.

Replies are listed 'Best First'.
Re: How to detect a hash container key from a referenced sub value
by FalseVinylShrub (Chaplain) on Dec 03, 2009 at 05:58 UTC

    Hi

    Forgive me if I've missed something obvious, but why don't you just create a subroutine that accepts a hash and calculates the sum:

    # # Example code, not tested at all! # my %phrases = ( "phrase 1"=>{ qty=1, qtySum=5, qtyCont=8, qtyTotal=\&qtyTotal }, # ... ); sub qtyTotal{ my $qty = 0; my $phrase = shift; foreach my $qType ('qty', 'qtySum', 'qtyCont'){ $qty += $phrases{$qType} if exists $phrases{$qType}; } return $qty; } # and call like this: my $total = qtyTotal($phrases{'phrase 1'});

    Seems like you're trying to do something in a somewhat object-oriented way. If you want to do that, you should look into making actual objects which comes with mechanisms for the method to know which object it was called on:

    # incomplete example, not tested sub total { my $self = shift; my $total = 0; foreach my $key (qw{blah blah blah}) { $total += $self->{$key} } return $total; } print $phrase->total();

    If that looks like what you're trying to do, you need to read up on Perl OO... I suggest you post again if you need pointers on where to start.

    Edits:

    • initialise $total in my method example.
    • Add missing operand to += in qtyTotal

    HTH

    FalseVinylShrub

    N.B. Please review and test code, and use at your own risk... If I answer a question, I would like to hear if and how you solved your problem.

      First, I want to say, I've made some typos in my code. I've used '=' instead of '=>' in hashes and I've forgot to apply a value in the subroutine. I fixed it now.

      Anyway.

      The obvious, as you say, yes. That's how I usually do it. I, usually, create a sub, supply the values explicitly to it and get the result. I just wanted to see, if it's possible to do it implicitly, also, I need that total value in the variable, because I am going to sort it by qtyTotal in dec order, by using a sort function. If it's not going to be automatically updated, I figure that I have to create an other temp variable, every time I use that sort function. I just wanted to see, if could it be done in a more elegant way. Also, it will eliminate any bugs, that could be caused by forgetting of updating a qtyTotal in that variable.

      I guess, you are right about creating object instead of just using a regular variable.

      I was hesitant with that because, the only time, I've used objects is when I refer to other libraries or modules and what always stopped me was that, i might be wrong, the requirement to create a module, describing the object in the separate file instead of just ability to make an object in the same file as the script. :(
        what always stopped me was that, i might be wrong, the requirement to create a module, describing the object in the separate file instead of just ability to make an object in the same file as the script.

        You don't necessarily need separate files.  Separate packages, yes, but you can have more than one package in the same file (if you really want):

        #!/usr/bin/perl package Foo; sub new { my $class = shift; return bless { @_ }, $class; } # ... package Bar; sub new { my $class = shift; return bless { @_ }, $class; } # ... package main; # the actual script my $foo = Foo->new( myattr => "foo" ); # create an obj use Data::Dumper; print Dumper $foo; __END__ $VAR1 = bless( { 'myattr' => 'foo' }, 'Foo' );
Re: How to detect a hash container key from a referenced sub value
by shmem (Chancellor) on Dec 03, 2009 at 11:43 UTC
    So, the question, as I mentioned above and as you saw in the sub, how does the subroutine know it's container hash key, in order to calculate the right key's qty values?

    Various ways.

    • with closures. Don't stuff a reference to qtyTotal() into the "qtyTotal" slot, but a subroutine which calls qtyTotal with the right argument. The argument could be either

      1. the key for that container:
        my %phrases = ( "phrase 1" => { qty => 1, qtySum => 5, qtyCont => 8, }, "phrase 2" => { qty =>10, qtyCont =>34, }, "phrase 3" => { qty =>1, }, ); for my $k (keys %phrases) { # update # $phrases{$_}->{ qtyTotal} = sub { qtyTotal($_) } $phrases{$k}->{ qtyTotal} = sub { qtyTotal($k) } } sub qtyTotal{ my $qty = 0; my $key = shift; my $phObj = $phrases{$key}; foreach my $qType ('qty', 'qtySum', 'qtyCont'){ $qty += $phObj->{$qType} if exists $phObj->{$qType}; } return $qty; }
      2. the container itself (better):
        for my $k (keys %phrases) { # update # my $ref = $phrases{$_}; my $ref = $phrases{$k}; $ref->{ qtyTotal} = sub { qtyTotal($ref) } } sub qtyTotal{ my $qty = 0; my $phObj = shift; foreach my $qType ('qty', 'qtySum', 'qtyCont'){ $qty += $phObj->{$qType} if exists $phObj->{$qType}; } return $qty; }
        That way the subroutine qtyTotal() doesn't need to know about %phrases and could be in a different scope. And you could e.g. have "aliases" in your %phrases hash, saying $phrases{"phrase 4"} = $phrases{"phrase 2"}.

      In either case, you need the reference/dereference trick to interpolate the results of a subroutine call into a double-quoted string:
      print "${ \$phrases{'phrase 2'}->{'qtyTotal'}->() }\n";
    • make the containers into objects, so they "know themselves" (best):
      package Qty; sub new { my $class = shift; bless { @_ }, $class; } sub qtyTotal { my $phObj = shift; foreach my $qType ('qty', 'qtySum', 'qtyCont'){ $qty += $phObj->{$qType} if exists $phObj->{$qType}; } return $qty; } package main; my %phrases = ( "phrase 1" => Qty->new( qty => 1, qtySum => 5, qtyCont => 8, ), "phrase 2" => Qty->new( qty =>10, qtyCont =>34, ), "phrase 3" => Qty->new( qty =>1, ), ); print "${ \$phrases{'phrase 2'}->qtyTotal() }\n";
      This approach has the advantage that you can add more methods (e.g. qtyAverage()) to the Qty class and use them without having to change the way in which you populate %phrases.

    As a rule of thumb - if you ask yourself how does X know it's Y, the object approach suits best.

      Hello, so to summarize everything, I've learned a few things:
      1. I learned that in order to use aware functions on complex variables, it's better to use objects with methods.
      2. I learned that packages can be in the same file as the main script.
      3. I learned that bless function makes methods work in objects.
      One thing that confused me in bless function was the way it was written:
      bless { @_ }, $class;
      I couldn't understand it, forgetting that functions can be used without parentacies (I sined myself, doing that) and arrays can be applied and converted to hashes that way.

      Then, after digesting all of the information, you guys gave me and taking a closer look, I've had a revelation that { @_ } is nothing other then a regular anonymous hash reference of @_ array being converted to hash ref by {} braces and a $class is a reference to Self object.

      So, not exactly, but in other words:
      my %hash = @_; bless(\%hash, $selfObjRef);
      or
      bless({@_[1..$#_]}, $_[0]);
      One thing I am a little confused still is, when I add values to the variable this way:
      my %phrases = ( "phrase 1" => Qty->new( qty => 1, qtySum => 5, qtyCont => 8 ), "phrase 2" => Qty->new( qty =>10, qtyCont =>34 ), "phrase 3" => Qty->new( qty =>1 ) );
      Can I still access them by (I didn't test it yet, still trying to digest everything)?:
      print $phrases{'phrase 2'}->{'qtySum'}; print $phrases{'phrase 1'}->{'qtyCont'}; print $phrases{'phrase 3'}->{'qty'};
      I thought about coding the way, you've described in your first 2 methods with closures before posting to perlmonks, but things that stopped me were a curiosity if it's possible to do it without passing parameters, informing about it's container to a function and also I needed the function to be able to accept explicit parameters, because I do not always need a total of all qtys. Sometimes, I would need a total of qty and qtyCont and sometimes, a qty and qtySum, etc..

      You guys've made a point that it's done with objects, and I need to read more to understand them better and experiment a little bit.

      While reading, your first 2 methods, I remembered of using static variables in regular subs, which, in turn made me think about ability to pass explicit values without polluting them with container info to an anonymous sub, which passes all of the values to the sub, calculating qtys.

      my %phrases = ( 'phrase 1'=>{ qty=>1, qtySum=>5, qtyCont=>8, qtyTotal=>sub{return &qtyTotal('phrase 1', @_);} }, 'phrase 2'=>{ qty=>10, qtyCont=>34, qtyTotal=>sub{return &qtyTotal('phrase 2', @_);} }, 'phrase 3'=>{ qty=>1, qtyTotal=>sub{return &qtyTotal('phrase 3', @_);} } );
      I've used a key value, but I understand, that the current branch reference can also be used.

      I realize, that this is an inefficient solution, but this is the solution that allows using pseudo methods without an actual objects.

      I need to play some more with objects to completely grasp the subject, while the pseudo method is a temporary solution, until I completely understand objects and update my program to use objects instead.

      Thank You all for your help.

        Then, after digesting all of the information, you guys gave me and taking a closer look, I've had a revelation that { @_ } is nothing other then a regular anonymous hash reference of @_ array being converted to hash ref by {} braces and a $class is a reference to Self object.

        No, not quite. See "Method Invocation" in perlobj. For class methods, the first argument for a method is the class (just its name); for objects, it is the object. Not a reference - just like that. For class methods, the class name is a literal, and the object is a reference already. The arrow operator "->" makes the passing of the first parameter implicit, so the next two are equivalent:

        $obj = MyClass->new( %params); $obj = MyClass::new( 'MyClass', %params);
        Can I still access them by (I didn't test it yet, still trying to digest everything)?:

        Yes, since an object is just a reference blessed into a Namespace. Otherwise, it behaves just like any reference. Again, read perlobj and related material.

        qtyTotal=>sub{return &qtyTotal('phrase 1', @_);}

        I realize, that this is an inefficient solution, but this is the solution that allows using pseudo methods without an actual objects.

        The use of the ampersand "&" as a sigil to suroutine calls makes passing @_ implicit, but only if the call has an empty parameter list. Consider:
        $\ = $/; # see perlvar sub b { print join" ",@_ } sub e { &b } # @_ passed implicitly sub f { &b( "got") } # parens necessary! sub g { b "got", @_ } # parens may be omitted, if sub b # has been seen earlier @l = (1...3); e @l; f @l; g @l; __END__ 1 2 3 got got 1 2 3

        So, use "&" only if you mean to pass @_ (and only @_) implicitly.

        And it's not ineffective, I'd say, but neither is it "pseudo-methods" - that is calling anonymous subroutines. A pseudo method (call) would be something like the following:

        sub b { print join" ", @_ } "b"->(1..3)

        Further reading: perlboot, perlref, perlreftut, perltoot, ...

Re: How to detect a hash container key from a referenced sub value
by 7stud (Deacon) on Dec 03, 2009 at 07:27 UTC

    Forgive me if I've missed something obvious, but why don't you just create a subroutine that accepts a hash and calculates the sum.

    My thoughts too. If you employ Phrase objects, you can even make it so that the total will automatically update as you add a new quantity key, e.g. "qtyCont":

    use strict; use warnings; use 5.010; use Phrase; my $phr1 = Phrase->new(); $phr1->quantities("qty", 1); say $phr1->total_quant; $phr1->quantities("qtySum", 5); say $phr1->total_quant; $phr1->quantities("qtyCont", 8); say $phr1->total_quant; --output:-- 1 6 14

    /\/\/\/\/\/\/\

    package Phrase; use strict; use warnings; use 5.010; sub new { my $self = {}; $self->{QUANTITIES} = {}; $self->{TOTAL_QUANT} = 0; bless($self); return $self; } sub total_quant { my $self = shift; return $self->{TOTAL_QUANT}; } sub quantities { my $self = shift; my $key = shift; if (@_) { my $quant = shift; $self->{QUANTITIES}->{$key} = $quant; $self->{TOTAL_QUANT} += $quant; } return $self->{QUANTITIES}->{$key}; } 1;
      So, I get it now, the verdict is that I have to create a module (package) to describe an object. I guess, I should read about objects. I've made attempts to learn it before, but there were few things that stopped me. One that I described in reply to FalseVinylShrub, the inability to use the same script file for describing an object and also, I couldn't understand the purpose of the bless function.

        I couldn't understand the purpose of the bless function

        Normally, if you have a hash reference:

        my $hash = { a => 1, b => 2 };

        you can write:

        say $hash->{a};

        but you cannot use a hash reference to call a function, e.g.

        $hash->some_func()

        If you look at the code that creates a class, the new() function creates a variable called $self and assigns it a reference to a hash. bless() makes it so that you can use the hash reference to call functions.

        An object is different from any other data type in Perl in one and only one way: you may dereference it using not merely string or numeric subscripts with simple arrays and hashes...

        $aref->[0]
        $href->{'a'}

        ...but with named subroutine calls...

        $href->some_func()

        In a word, with methods.

        perltoot