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

I've got a horribly complex nested data structure, which isn't working...

Early on in my object I create an array of passed-in objects by pushing them onto the array like this:

(Each element in the array is an array of two elements.)

sub add_log { my ($self, $parser, $coderef) = @_; push @{$log_list{refaddr $self}}, [ $parser, $coderef ]; return 1; }

A little later on I'm trying to use a method in that object like this:

sub find_message { my ($self, $argref) = @_; # Parse the arguments, and get all the message info. my $msg_info = $self->_parse_args($argref, 1); # The '1' means +throw an error if we don't have any info. # Find the message in the first log. if ($log_list{refaddr $self}->[0][0]->find_message_info($msg_info) +) { # Do something. return 1; } else { return 0; } }

Finally, I've got the following test script:

my $tracer = Mail::Log::Trace::Postfix->new({log_file => 't/data/lo +g'}); my $object = Mail::Log::Trace::Multiple->new(); $object->add_log( $tracer, sub {} ); my $condition = {to_address => '<0001@example.com>', from_start => +1}; ok($object->find_message($condition) , 'Mail::Log::Trace::Multiple: + Find message from "to" field.'); ok($tracer->find_message_info($condition), 'tracer test.');

Which fails the first test but not the second. So I know the method works. (And it wouldn't compile at all if I wasn't calling it correctly: I've tested that by changing the spelling.)

So I am absolutely sure the problem is that the line $log_list{refaddr $self}->[0][0]->find_message_info($msg_info) is mis-behaving. How it's mis-behaving I'm not so sure about. (As far as I can tell, it's operating as a no-op.)

Anyone have any ideas on how to get that line to correctly function? Or what I'm doing wrong?

- DStaal

Replies are listed 'Best First'.
Re: Calling a subroutine via multiple dereferences.
by jethro (Monsignor) on Nov 03, 2008 at 15:33 UTC
    In your test case you use $condition directly with find_message_info, while when called via find_message it goes through _parse_args(). Which means your conclusion is a bit premature, if I read that correctly.
      Well, since I happen to know that Mail::Trace::Postfix->find_message_info() also calls _parse_args(), I don't worry about that. (In fact, since they are both subclasses of the same class, they call the same _parse_args().) Sorry, I should probably have mentioned that.
        So in find_message _parse_args is called twice. Are you sure that _parse_args(_parse_args($data)) equals _parse_args($data) ? I'm not saying this is the problem, but it might be worthwile to print out the contents of $msg_info with Data::Dumper. I know from mistakes I make that they turn up at unexpected places.
Re: Calling a subroutine via multiple dereferences.
by sflitman (Hermit) on Nov 04, 2008 at 02:41 UTC
    My $0.02 is your code is too complex. Break it down into more than one hash with descriptive names. Why are you using Scalar::Util's refaddr, by the way, it's very unusual and will give different results on different platforms. Why not hash $self itself, won't you get different keys from its stringification?

    SSF

        scalar() may not be unique, and may change over the lifetime of the object. For instance, in this case, I've got the scalar conversion overloaded to return the class name and some basic info on the state of the class. Useful for debugging and error returns. However it is quite easy to set up two different objects so that they appear the same. Also, calling one of a few different methods will change what would be returned. (Which leads to a self-referential loop: To get the scalar value, you have to know the scalar value...)

        refaddr() by contrast is stable: The object will always be in a particular location, until it is no longer that object. And no two objects may have the same location at the same time. So it is much safer to use. I don't actually care what the key is, as long as it is unique and stable, and refaddr provides that.

        As for complex... Past the inside-out object, it's just an array of arrays, and the second level array is just to keep things associated. I actually looked at setting up two different arrays, but this is much simpler to keep track of.

Re: Calling a subroutine via multiple dereferences.
by wol (Hermit) on Nov 04, 2008 at 18:03 UTC
    So I am absolutely sure the problem is that the line $log_list{refaddr $self}->[0][0]->find_message_info($msg_info) is mis-behaving.
    Well, I can't say I really understand it, so could well be.

    Are you running strictly and with warnings? If not, then I think it's possible that your dereferencing is falling off the side of your data structure before it gets to the end of it, with no diagnostic. Try printing out the following expressions:

    ref $log_list{refaddr $self} ref $log_list{refaddr $self}->[0] ref $log_list{refaddr $self}->[0][0] ref $log_list{refaddr $self}->[0][0]->find_message_info
    to see whether you're getting as far as a reference of type CODE.

    --
    .sig : File not found.

      jethro (above) solved my problem: I was expecting behavior from one of my functions that no longer was the case...

      Of course I am using strict and warnings. Second thing I type in any Perl file. (After the #! line.) ;)

      I had actually played with that: sending the different references to Data::Dumper and seeing what I got. (As well as trying different variations on the dereferencing, and seeing what works.) The dereference string above correctly dereferences to a method in an object stored in a two-dimensional array in a hash.

      The code above now works, now that I've got _parse_args() able to parse it's own output again. (Which means find_message_info() is working correctly, which it wasn't.)