http://qs321.pair.com?node_id=156027

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

The task is to compare two variables in such a way that if they are both undef, then they should be treated as equal.

($a == $b) gives me warning about using undefined values.
(defined($a) && defined($b) && $a == $b) is wrong
((defined($a) && defined($b) && $a == $b) || (not defined($a) && not defined($b))
is ugly.

This is used to detect changes in a database. This leads to the requirement that NULL must be equal NULL (which isn't right in the general case of course).

Replies are listed 'Best First'.
(Ovid) Re: comparing two values under 'perl -w'
by Ovid (Cardinal) on Apr 02, 2002 at 16:02 UTC

    There are several ways to solve this. One is to turn off warnings within the scope in which you have defined the variables. The following solution should work under most versions of Perl. It should also match your requirement that undef match undef.

    use strict; use warnings; my ($w, $x, $y, $z)=(undef,undef,7,7); if ( equal( $x, $y ) ) { print "Equal\n"; } else { print "Not equal\n"; } if ( equal( $w, $x ) ) { print "Equal\n"; } else { print "Not equal\n"; } if ( equal( $y, $z ) ) { print "Equal\n"; } else { print "Not equal\n"; } sub equal { my ( $first, $second ) = @_; local $^W; # < 5.6 no warnings; # >= 5.6 return $first == $second; }

    Of course, I've encapsulated that in a sub to minimize its effects.

    Cheers,
    Ovid

    Update: Fixed code per some pointers by VSarkiss.

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Re: comparing two values under 'perl -w'
by demerphq (Chancellor) on Apr 02, 2002 at 16:12 UTC
    Shouldnt use $a and $b as they can have bizzare side. And i have no idea if this is more or less elegant than yours... (IMO its easier to understand, but then again I like ternary ops.)
    my $equal=defined($x) && defined($y) ? $x==$y : !defined($x) && !defin +ed($y) ? 1 : 0;

    Yves / DeMerphq
    ---
    Writing a good benchmark isnt as easy as it might look.

      Can we not just skip the secondary ternary here?

      if(defined($x) && defined($y) ? $x==$y : !defined($x) && !defined($y)) + {
        Doh.

        Good point. I certainly could have.

        :-)

        Yves / DeMerphq
        ---
        Writing a good benchmark isnt as easy as it might look.

Re: comparing two values under 'perl -w'
by particle (Vicar) on Apr 02, 2002 at 15:43 UTC
    ( defined $a and defined $b and $a == $b )
    and and or are the same as && and ||, except their precedence is much lower.

    don't use $a and $b. they're for sort

    ~Particle ;Þ

      This one misses the point that two undefs should turn out as equal.
Re: comparing two values under 'perl -w'
by Biker (Priest) on Apr 02, 2002 at 15:55 UTC

    With all due respect, kappa.

    When you aim to violate the underlying logic, you end up with code like that. You already state that NULL is not equal to NULL. This is also true for undefined; undefined is not equal to undefined.

    If you force it to be that way, so be it. But Perl wasn't really written with that in mind, so it will be difficult to make your code elegant.


    Everything went worng, just as foreseen.

      btw,
      ~% perl -we 'print "Really???\n" if undef == undef;' + Use of uninitialized value at -e line 1. Use of uninitialized value at -e line 1. Really??? ~%

      I know that undef in numerical context is just 0, but...
        Wtith all due respect thats a big but...

        :-)

        Hmm. No pun intended.

        Yves / DeMerphq
        ---
        Writing a good benchmark isnt as easy as it might look.

Re: comparing two values under 'perl -w'
by TheDamian (Vicar) on Apr 02, 2002 at 22:53 UTC
    Not that it solves your immediate problem (well, not in a practical timeframe anyway), but Perl 6 will have a much easier way to handle this kind of comparison:
    $x =~ $y

    From the latest Exegesis (which is now finished and coming RSN to a perl.com near you):

    The humble Perl 5 "match a string against a regex" operator is promoted in Perl 6 to a "smart-match an anything against an anything" operator. So now:
    if ($val1 =~ $val2) {...}

    works out the most appropriate way to compare its two scalar operands and does so. The result might be a numeric comparison ($val1 == $val2) or a string comparison ($val1 eq $val2) or a subroutine call ($val1.($val2)) or a pattern match ($val1 =~ /$val2/) or whatever else makes the most sense for the actual run-time types of the two operands.

    This new turbo-charged "smart match" operator will also work on arrays and hashes and lists:

    if @array =~ $elem {...} # true if @array contains $elem if $key =~ %hash {...} # true if %hash{$key} if $value =~ (1..10) {...} # true if $value is in the list if $value =~ ('a',/\s/,7) {...} # true if $value is eq to 'a' # or if $value contains whitespa +ce # or if $value is == to 7

    That final example illustrates some of the extra intelligence that Perl 6's =~ has: when one of its arguments is a list (not an array), the "smart match" operator recursively "smart matches" each element and ORs the results together, short-circuiting if possible.

    Larry hasn't said so explicitly, but I'm pretty sure that two undefs will also compare equal (silently) under the new =~.
      Thank you for the peek into the upcoming Exegesis. I eagerly look forward to spend another hour reading and dreaming for the best Perl ever :))
      But I wanted to note that equalness of two undefs is very useful but contradicts with generally accepted laws of three-state logic, does it not?
        Thank you for the peek into the upcoming Exegesis.

        ...which is now fully on-line.

        But I wanted to note that equalness of two undefs is very useful but contradicts with generally accepted laws of three-state logic, does it not?
        It sure does. There was a long discussion around that issue on the perl6-language mailing list...starting here.

        As I recall, there wasn't a clear outcome to the debate (surprise, surprise), but at the time Larry didn't seem keen on undef taking on the tri-state semantics of NULL.

Re: comparing two values under 'perl -w'
by RMGir (Prior) on Apr 02, 2002 at 15:50 UTC
    Interesting... Offhand, I can't think of anything, so I'd suggest:
    sub equalOrBothNull { my ($a, $b) = @_; ((defined($a) && defined($b) && $a==$b) || (!defined($a) && !defined($b)); }
    That way, you can at least hide the problem and make the main code more readable.

    By the way, I'm pretty sure

    not defined($a) && not defined($b)
    is not what you want.
    $ perl -MO=Deparse -e'not defined($a) && not defined($b)' not defined $a && !defined($b); -e syntax OK
    Not doesn't bind as tightly as !.
    --
    Mike
Re: comparing two values under 'perl -w'
by Rhose (Priest) on Apr 02, 2002 at 16:38 UTC
    How about creating a sub to do this?

    #!/usr/bin/perl -w use strict; sub SpecialEqual { my $pVal1 = shift; my $pVal2 = shift; return 1 unless (defined $pVal1 or defined $pVal2); return (defined $pVal1 and defined $pVal2 and $pVal1 == $pVal2); } print 'Testing: 1,1 = ',SpecialEqual(1,1),"\n"; print 'Testing: 1,2 = ',SpecialEqual(1,2),"\n"; print 'Testing: 2,1 = ',SpecialEqual(2,1),"\n"; print 'Testing: 2,2 = ',SpecialEqual(2,2),"\n"; print 'Testing: 2,2.5 = ',SpecialEqual(2,2.5),"\n"; print 'Testing: 2.5,2 = ',SpecialEqual(2.5,2),"\n"; print 'Testing: 2.5,2.5 = ',SpecialEqual(2.5,2.5),"\n"; print 'Testing: undef,1 = ',SpecialEqual(undef,1),"\n"; print 'Testing: 1,undef = ',SpecialEqual(1,undef),"\n"; print 'Testing: undef,undef = ',SpecialEqual(undef,undef),"\n";

    Update:

    Maybe I should have read RMGir's reply, yes? *Smiles*

      I like this best of all :)
      I works (good) and is readable (even more good). The one with ternary operator is shorter, thank you demerphq, but is a little too complicated to my taste. I like ?: myself, but not when they're nested. This code will probably be read by different people including perl novices.
        Er, I take minor issue with the term "nested". I too tend to avoid nested ternary ops but I generally think of the above example as "chained" and not nested.

        my $x=$y ? $z ? $t : $u : $v; #nested my $x=!$y ? $v : $z ? $t : $u; #chained
        Oh and I dont really understand why people are so superstitious about ternary ops. Many non developers are even familiar with them as they come up often in excel cell formula if(Condtion,yesresult,noresult). OTOH if you have maintainence issues to consider then whatever minimizes headaches...

        :-)

        Yves / DeMerphq
        ---
        Writing a good benchmark isnt as easy as it might look.

Re: comparing two values under 'perl -w'
by gav^ (Curate) on Apr 02, 2002 at 15:53 UTC
    What about?
    if (!(defined $a && defined $b) || $a eq $b) { print "equal"; }

    gav^

      What if $a = undef and $b = 3?
      We get equal?
Re: comparing two values under 'perl -w'
by I0 (Priest) on Apr 03, 2002 at 04:01 UTC
    (not defined($a) && not defined($b)) || ($a == $b)

      That will still throw an uninit warning if one variable is defined and the other undefined.