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

Dear Monks,

I've just discovered a very interesting command in Python. The 'is' command. It is not the same as '==' because 'is' does not check for equality. Instead it checks whether the same memory reference is used in each case. The theoretical basis for using the 'is' command is quite nice in many circumstances (particularly in relation to object orientated programming). I am assuming that you can do exactly the same in Perl using a ref command in conjunction with '=='. It is a classic case of where the theory overrides the non requirement at the code level. It is worth being aware of as a Perl programmer. Personally I think Python has it right on that one, although I do think that Python has too many commands. Are there any other examples like this that I can follow? It might make me a better Perl programmer.

Replies are listed 'Best First'.
Re: Python 'is' command
by AnomalousMonk (Bishop) on Aug 14, 2019 at 16:04 UTC
    The [Python] 'is' command ... checks whether the same memory reference is used in each case. ... I am assuming that you can do exactly the same in Perl using a ref command in conjunction with '=='.

    No ref command need apply.

    c:\@Work\Perl\monks>perl -wMstrict -le "my $ar_a = [ qw(a b c d) ]; my $ar_b = $ar_a; ;; print 0+$ar_a; print 0+$ar_b; ;; print 'is equal' if $ar_a == $ar_b; " 22632908 22632908 is equal
    This works due to numeric coercion of a reference.


    Give a man a fish:  <%-{-{-{-<

Re: Python 'is' command
by haukex (Bishop) on Aug 14, 2019 at 20:14 UTC
    I am assuming that you can do exactly the same in Perl using a ref command in conjunction with '=='.

    Not quite, since ref returns the type of the reference, not its address. You'll need Scalar::Util's refaddr for the latter. By default, Perl references used in numeric context will return the memory address (perlref), however, if the Perl objects use operator overloading, this is no longer true - see my example here.

    If you look at the Python docs, you'll see that if the class defines a __eq__ method, it is used for == comparisons, and the fallback is to check if it's the same object, i.e. it's basically the same behavior as Perl's operator overloading. Python's is is basically what BillKSmith implemented here (and it's the only "entirely correct" answer in this thread).

    Note that the general concept of "does one object equal another" is actually a lot more difficult than it sounds. When are two database handles equal - when they connect to the same database, or they have the same state, and so on? When are two objects loaded through an ORM like DBIx::Class equal - when their primary key is equal, or when all of their fields are equal, and so on? In such cases it's easier to code a custom comparator instead of the class attempting to provide a sensible default.

Re: Python 'is' command
by BillKSmith (Prior) on Aug 14, 2019 at 17:00 UTC
    My 'is' function is overkill for my test case, but it demonstrates the functionality you describe.
    use strict; use warnings; use Test::Simple tests => 2; sub is { use Scalar::Util qw(refaddr); return (refaddr $_[0] == refaddr $_[1]); } my $x = 3; my $y = 3; my $x_ref = \$x; my $test_ref_1 = \$x; my $test_ref_2 = \$y; ok( is($test_ref_1, $x_ref), 'Same' ); ok( !is($test_ref_2, $x_ref), 'Different' );
    Bill
      ... 'is' ... is overkill ...

      I strongly agree! What's the point of bringing Scalar::Util::refaddr() et al to the party when  == != (and all the other numeric comparators, in the unlikely event they would be of any use) seem to manage just fine:

      c:\@Work\Perl\monks>perl use strict; use warnings; use Test::Simple tests => 4; 1..4 sub is { $_[0] == $_[1] } my $x = 3; my $y = 3; my $x_ref = \$x; my $test_ref_1 = \$x; my $test_ref_2 = \$y; ok( is($test_ref_1, $x_ref), 'is Same' ); ok( !is($test_ref_2, $x_ref), 'is Different' ); ok( $test_ref_1 == $x_ref, '==' ); ok( $test_ref_2 != $x_ref, '!=' ); __END__ ok 1 - is Same ok 2 - is Different ok 3 - == ok 4 - !=


      Give a man a fish:  <%-{-{-{-<

        What's the point of bringing Scalar::Util::refaddr() et al to the party when == != (and all the other numeric comparators ... seem to manage just fine

        Because it's the only solution in the whole thread that still works even in the presence of operator overloading? ;-)

        use warnings; use strict; package Foo { my $x; use overload '<=>'=>sub{1}, 'cmp'=>sub{1}, '0+'=>sub{++$x}; } my $x = bless {}, 'Foo'; my $y = $x; print $x==$y ? "True\n" : "False\n"; # False use Scalar::Util qw/refaddr/; sub is { return refaddr $_[0] == refaddr $_[1] } print is($x,$y) ? "True\n" : "False\n"; # True
        Your comment clarifies exactly what I meant. However, the documentation of the refaddr function in Scalar::Util assures us that our operatores are comparing addresses. The user documentation for references (perlref) makes no such claim.
        Bill

        you assume references. I don't know what python's is() does but I would expect any Perl is() to handle both is($x,$y) and is(\$x, \$y).

Re: Python 'is' command
by Fletch (Chancellor) on Aug 14, 2019 at 13:25 UTC

    Actually for references '==' is comparing for the same instance (referential equality). For recent enough perls the smartmatch ~~ operator can do at least a first level check for structural or object equality. If you want to compare if they more deeply nested structures contain the same contents you need do more work; see the FAQ "How do I test whether two arrays or hashes are equal?".

    $ perl -MTest::More -dE 0 [...] DB<1> $a = { qw/a 1 b 2/ } DB<2> $b = { qw/a 1 b 2/ } DB<3> x $a 0 HASH(0x7f9140905ad0) 'a' => 1 'b' => 2 DB<4> x $b 0 HASH(0x7f914090b698) 'a' => 1 'b' => 2 DB<5> x $a == $b 0 '' DB<6> x is_deeply( $a, $b ) ok 1 0 1 DB<7> x is_deeply( $a, {} ) not ok 2 # Failed test at /Users/nbkawb9/perl5/lib/perl5/Test/Builder.pm line + 152. # Structures begin differing at: # $got->{a} = '1' # $expected->{a} = Does not exist 0 0 DB<8> q

    Additionally: For real fun check out Lisp which has several predicates for different amounts of equality.

    Edit: Tweaked slightly ambiguous "they" to more explicit wording.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      For recent enough perls the smartmatch ~~ operator

      Smart matching was retroactively documented as experimental since v5.16 (2012), and once in a while there are discussions on P5P on completely overhauling its behavior. I wouldn't recommend it, as code relying on it may break on newer versions of Perl. As of v5.18 using it will generate warnings about its experimental status.

      I also mentioned that the behavior of == can be changed with operator overloading in my other replies in this thread.

Re: Python 'is' command
by Jenda (Abbot) on Aug 15, 2019 at 17:58 UTC

    It helps to use correct terminology. It's not a command, it's an operator. Also the programming is object oriented.

    I can't decipher the "It is a classic case of where the theory overrides the non requirement at the code level." which is maybe why I don't see what it is Python is supposed to have right.

    Jenda
    1984 was supposed to be a warning,
    not a manual!

Re: Python 'is' command
by syphilis (Bishop) on Aug 16, 2019 at 07:42 UTC
    Hi betmatt,
    Not sure how well this would fit the bill:
    use strict; use warnings; use Math::BigInt; # for demo use Inline C => Config => BUILD_NOISY => 1, ; use Inline C => <<'EOC'; int is(SV * a, SV * b) { if(a == b) return 1; if(SvROK(a) && SvROK(b)) { if(SvRV(a) == SvRV(b)) return 2; } return 0; } EOC my $x = 15; my $y = 14; my $r1 = \$x; my $r2 = \$x; my $r3 = \$y; my $m1 = Math::BigInt->new(7); my $m2 = $m1; my $m3 = Math::BigInt->new(7); print is($x, $y), "\n"; print is($r1, $r2), "\n"; print is($r1, $r1), "\n"; print is($r1, $r3), "\n"; print is($m1, $m2), "\n"; print is($m3, $m2), "\n";
    Cheers,
    Rob
Re: Python 'is' command
by kcott (Bishop) on Aug 15, 2019 at 08:57 UTC

    G'day betmatt,

    "... do exactly the same in Perl using a ref command in conjunction with '=='."

    Depending on context, it may be worthwhile using ref to ensure you're actually comparing references; however, it's not needed for the comparison.

    Instead of ==, I'd probably choose eq and do the comparison test like this:

    $ref1 eq $ref2

    Here's a short example:

    $ perl -E ' my @x = ({}, []); my $y = $x[0]; say for @x, $y; say $_->[0] eq $_->[1] ? "Y" : "N" for [@x], [$x[0], $y]; ' HASH(0x600003a70) ARRAY(0x600076050) HASH(0x600003a70) N Y

    — Ken

      Instead of ==, I'd probably choose eq

      eq can be overloaded just like ==, as I showed here, and is therefore more like Python's == instead of its is.

        I'm familiar with overloading. I was just showing another way to do it which, as far as I could see, no one else had mentioned.

        — Ken

Re: Python 'is' command
by daxim (Curate) on Aug 16, 2019 at 10:28 UTC
    Are there any other examples like this that I can follow?
    Yes! See the comprehensive is_equal method in perl5i::Meta (source). That's what people usually have in mind when they talk about equality.

    All the solutions shown in this thread involving references are not general enough due to wrong assumptions, see FAQ in Object::ID. (I think the original Python behaviour also suffers from the problem, but I haven't checked.) Again, the correct solution is also part of perl5i (source).

    perl5i is full of "done right" pieces of code. Monks, study it, even if you never intend to run it.

      All the solutions shown in this thread involving references are not general enough due to wrong assumptions, see FAQ in Object::ID.

      Really ? ... *all* of them ?
      I think the OP is concerned about determining whether 2 existent objects are the same, not whether a current object has the same "address" as an obsolete one.
      So long as 2 reference addresses of currently existent objects are being compared, then I don't really see the relevance of the FAQ in Object::ID.

      Cheers,
      Rob
      > see FAQ in Object::ID

      Interesting but not relevant here.

      The OP wants to compare two existing objects with an is operator.

      References can't be reused if the objects haven't been garbage collected yet.

      On a side note: why should one want to keep track of numeric or string representations of ref-adresses?

      Keeping the original ref in a hash value would keep it protected from destruction.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice