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

Update: Thanks to dave_the_m, ikegami and hossman I have found the problems and fixed them in my code. The warnings on $a and $b were not the issue. Using ikegami's suggestion removed those warnings.

The problem was that the abs function returned a scalar and not an object. This is the intended result for that function. After the first pass through reduce $a was a scalar and couldn't call abs().


Greetings! I'm getting some warnings and errors with a module function. I've searched around but haven't found any node or web page with a solution. Hopefully some eyes from a distance can see where I've gone wrong.

I'm using build 518 of ActiveState's Perl on Windows XP. I've included the smallest sample that recreates the problem in the read more section below. The messages I'm receiving are:

Name "Math::Interval::a" used only once: possible typo at C:\SRC\ivpm. +pl line 39. Name "Math::Interval::b" used only once: possible typo at C:\SRC\ivpm. +pl line 39. Can't call method "abs" on an undefined value at C:\SRC\ivpm.pl line 3 +9.

I've used Data::Dumper to examine the value of $row_ref and it is an array of three Math::Interval objects like I expect. Also, I'm able to call abs() for any value in the matrix. (Ex. $x->[1][1]->abs()).

Any idea where I've gone wrong? Also any idea why I'm getting the warnings for $a and $b from using reduce? Any code improvements are welcome. TIA!

#!/usr/bin/perl package Math::Interval; use warnings; use strict; use List::Util qw( max min reduce ); sub _eps_for { my ($num, $epsilon) = (shift) x 2; # copy arg to both vars $epsilon /= 2.0 while $num + $epsilon / 2.0 != $num; return $epsilon; } sub _interval { my ($min, $max) = ( min(@_), max(@_) ); return bless [$min - _eps_for($min), $max + _eps_for($max)], __PAC +KAGE__; } sub lb { my ($x) = @_; return $x->[0]; } sub ub { my ($x) = @_; return $x->[1]; } sub abs { my ($x) = @_; return max( abs($x->lb()), abs($x->ub()) ); } sub matrix_norm { my ($mat_ref) = @_; my @row_sums; foreach my $row_ref (@$mat_ref) { my $sum = reduce { $a->abs() + $b->abs() } @$row_ref; push @row_sums, $sum; } return max @row_sums; } package main; # getting the absolute value of a single interval works my $a = Math::Interval::_interval(0.333333, 0.333334); print 'abs([0.333333; 0.333334] is ', $a->abs(), "\n"; # getting the matrix norm for a matrix of intervals doesn't my $x = [ [ Math::Interval::_interval(3), Math::Interval::_interval(2), Math::Interval::_interval(3) ], [ Math::Interval::_interval(5), Math::Interval::_interval(9), Math::Interval::_interval(8) ], ]; print 'mat norm ', Math::Interval::matrix_norm($x), "\n";

Owl looked at him, and wondered whether to push him off the tree; but, feeling that he could always do it afterwards, he tried once more to find out what they were talking about.

Replies are listed 'Best First'.
Re: Can't call method "abs" on an undefined value.
by dave_the_m (Monsignor) on May 05, 2006 at 23:18 UTC
    (Two people so far have failed to realise that $a and $b are used by List::Util::reduce in the same way as sort.)

    Anyway, your immediate problem is that you don't include reduce in the 'use List::Util' import list. But after fixing that, your next problem is that $a and $b start off as objects, but after the first reduce, $a is now a simple number rather than an object, so $a->abs fails.

    Dave.

      Ah ha! I knew it was something simple I was missing. Thanks that gives me enough to take a stab at it again.

      reduce is included on the import list for List::Util in the main program I just forgot it in my sample. Adding it still gives the warning about $a and $b only being used once. I've updated the sample and my post.

      Owl looked at him, and wondered whether to push him off the tree; but, feeling that he could always do it afterwards, he tried once more to find out what they were talking about.

        In this case, the warnings are wrong. Those variables *are* used more than once, but List::Util sets them in a fashion that Perl can't detect at compile-time. (Something like *{caller() . '::a' = ...;.) You can get rid of the warnings by including the following in your package:

        $a = $a; $b = $b;

        Ignore the warnings when trying to find your error, they are misleading you. (But do change the variable name in my $a = ...; to something else!)

      Thanks! Adding reduce to the import list for List::Util changed the error message and it made a lot more sens.

      Owl looked at him, and wondered whether to push him off the tree; but, feeling that he could always do it afterwards, he tried once more to find out what they were talking about.

      That's not it either. If so, you'd get a different error message:
      >perl -e "$obj = 4; $obj->test()" Can't call method "test" without a package or object reference at -e l +ine 1.
        The OP gets the error message 'Can't call method "abs" on an undefined value' because reduce hasn't been imported, so isn't called, so perl just calls a block of code with $a being undef.

        I was saying that *if* the OP fixed her code so that reduce was actually called, then the original error would go away, but a new error message would appear similar to the one you describe, due to a further bug in her code

        Dave.

Re: Can't call method "abs" on an undefined value.
by hossman (Prior) on May 05, 2006 at 23:33 UTC

    The Documentation for the reduce function in List::Util suggests that the use of $a and $b in the block on line 39 is correct (and aparently intended to be "similar" to the way sort uses $a and $b) and totally unrelated to the use of $a on line#49.

    I would suspect the core of your problem is that you aren't importing "reduce" when you use List::Util qw( max min ); (since the docs for List::Util suggest that it doesn't export anything by default) but fixing that doesn't seem to have any affect.

    My best guess is that List::Util doesn't play nicely with blessed obejcts ... but that seems unlikely.

    ...oh, wait a minute. your abs() method isn't returning an object, it's returning a number. ... The first pass reduce makes will be on the first two obejcts in your list, which you've told it to call the abs() method on each, and sum the result. In the second pass $a will be the result of the first pass, and $b will be the next obect in your list ... maybe it's at that point that you get the error about not being able to call abs (because $a is just a simple scalar) ... but i would expect a slightly differnet error about it being unblessed, not about it being undefined ... hmmmmm.

      Thanks for your help. You are correct.

      Owl looked at him, and wondered whether to push him off the tree; but, feeling that he could always do it afterwards, he tried once more to find out what they were talking about.

Re: Can't call method "abs" on an undefined value.
by Hue-Bond (Priest) on May 05, 2006 at 23:12 UTC
    package Math::Interval; sub matrix_norm { my ($mat_ref) = @_; my @row_sums; foreach my $row_ref (@$mat_ref) { my $sum = reduce { $a->abs() + $b->abs() } @$row_ref; push @row_sums, $sum; } return max @row_sums; } package main; my $a = Math::Interval::_interval(0.333333, 0.333334);

    The problem is that you're using $a and $b. They are undefined in matrix_norm. strict does not yell about it because that variables are "reserved" for sort and sort of always exist. As soon as you change its names (recommended), you will know what's happening.

    $ perl -Mstrict -e 'print $a; die' Name "main::a" used only once: possible typo at -e line 1. Use of uninitialized value in print at -e line 1. Died at -e line 1. $ perl -Mstrict -e 'print $abc; die' ## die not reached Global symbol "$abc" requires explicit package name at -e line 1. Execution of -e aborted due to compilation errors.

    --
    David Serrano

Re: Can't call method "abs" on an undefined value.
by ikegami (Patriarch) on May 05, 2006 at 23:28 UTC
    Not an answer, but I wonder why you do
    sub _interval { my ($min, $max) = ( min(@_), max(@_) ); return bless [ ... ], __PACKAGE__; } Math::Interval::_interval(...)
    instead of
    sub new { my $class = shift; my ($min, $max) = ( min(@_), max(@_) ); return bless [ ... ], $class; } Math::Interval->new(...)

      It was cut and pasted from some code in Perl Hacks. Damn Damian, chromatic and Ovid for publishing a book with some very similar code to the module I'm writing.

      I was experimenting with some of the hacks and didn't want to clash with my existing new() constructor.

      Owl looked at him, and wondered whether to push him off the tree; but, feeling that he could always do it afterwards, he tried once more to find out what they were talking about.

Re: Can't call method "abs" on an undefined value.
by GrandFather (Saint) on May 05, 2006 at 23:08 UTC

    $a and $b are magic variables used by sort. Do not use them as general variables because your life will be unhappy (as you have found).

    If you change $a to $aInt and $b to $bInt you get syntax errors rather than warnings. Because I've no idea what $b is supposed to be (it's not used anywhere else) I can't advise the best way of dealing with the problem. Probably you need to pass $aInt and $bInt into the sub.

    Note, your problem could be demonstrated by:

    #!/usr/bin/perl use strict; use warnings; $a->abs();

    which sort of indicates that you didn't whittle the problem down very much.

    Update: Humpf, I did consider that $a and $b were being provided by one of the used modules, then thought "Who in their right mind would lay traps like that". Hey ho. (See Re: Can't call method "abs" on an undefined value.)


    DWIM is Perl's answer to Gödel