All things being equal, not all things are eqv

Perl 6 has a wealth of comparison operators so I don’t have to do a lot of work to test complex data structures. Consider the case where I want to test two lists. I might reach for the eq and think that it works. Here’s a demonstration of that in the REPL:

$ perl6
> ( 1, 2, 3 ) eq ( 1, 2, 4 )
False
> ( 1, 2, 3 ) eq ( 1, 2, 3 )
True

That looks like it works. The list that has different elements returns False and the one that looks like it has the same elements returns True. But, this is a quirk of my choice of test cases. Had I read the docs (and, admit it, many of us only do that when something expected happens), I would have known that eq coerces its arguments to strings and compares those. It is, after all, a string comparison operator (although in a multi-dispatch language you (and I) might guess such operators would multi-dispatch).

Using eq breaks if lists with different values stringify the same way, like these. Since the string coercion joins the elements with a space, spaces at the start or end of elements make it impossible to distinguish the separator from the values:

> ( 1, '2 ', 3 ) eq ( 1, 2, ' 3' )
True

That’s no good. But, this is why Perl 6 has more comparison operators. The eqv tests that two things have the same values:

> ( 1, '2 ', 3 ) eqv ( 1, 2, ' 3' )
False

This extends to deeper levels even. The values and the structure must be the same. Even if the ultimate scalar values are the same, they have show up in the structure in the same way:

> ( 1, 2, (4,5) ) eqv ( 1, (2,4), 5 )
False

And even to the object identity. And Array may hold the same values, but it is not the same thing:

> ( 1, 2, (4,5) ) eqv ( 1, 2, [4,5] )
False

Besides eq and eqv, Perl 6 has the === that tests for object identity. This was the start of my thinking on this post, but more about that in a moment. Even if the two lists have the same values and structure, they aren’t the same object:

> ( 1, 2, 3 ) === ( 1, 2, 3 )
False

But, I might have the same object stored differently. This Array and Scalar are the same because they store the same object:

> my @array = ( 1, 2, 3 )
[1 2 3]
> my $scalar = @array
[1 2 3]
> @array === $scalar
True

A change in one shows up in the other, which is almost a non-sensical thing to say because there is no “other”. It’s the same thing:

> $scalar[*-1] = 'Hamadryas'
Hamadryas
> @array
[1 2 Hamadryas]

And here where this came up for me. I need the permutations of a list (which, as you know, if the ordered structure that allows the same value to show up multiple times). Consider permutations of (1, 3, 3):

> (1,3,3).permutations
((1 3 3) (1 3 3) (3 1 3) (3 3 1) (3 1 3) (3 3 1))

I might not care that two permutations are (1, 3, 3) show up twice as long as I process one of them. Suppose I want the list of all the values of the sum of the first two items multiplied by the third:

> (1,3,3).permutations.map: { ($_[0] + $_[1])*$_[2] }
(12 12 12 6 12 6)

I’ve done that operation six times, but if I only care about the unique values, I’ve done a lot more work than I needed to do to get two value:

> (1,3,3).permutations.map( { ($_[0] + $_[1])*$_[2] } ).unique
(12 6)

In this example it’s not a big deal, but imagine much larger lists and more complicated operations. Consider how that makes junctions much larger:

> any( (1,3,3).permutations.map( { ($_[0] + $_[1])*$_[2] } ) ) > 13
any(False, False, False, False, False, False)

Curiously, this gives a Junction back instead of a Boolean, but I’ve asked about that in the docs repo and on RT.

So, I might try .unique:

> (1,3,3).permutations.unique
((1 3 3) (1 3 3) (3 1 3) (3 3 1) (3 1 3) (3 3 1))

That didn’t reduce the list, though. That method checks pairs of items with ===. That’s the check for object identity. The duplicated permutations look the same because they have the same values in the same order, but they are different objects.

No matter. I can tell .unique to compare elements any way that I like by using the :with adverb. If I use eqv, I end up with only the permutations I can distinguish:

> (1,3,3).permutations.unique( :with(&[eqv]) )
((1 3 3) (3 1 3) (3 3 1))

Now I process half the number of elements:

> any( (1,3,3).permutations.unique( :with(&[eqv]) ).map( { ($_[0] + $_[1])*$_[2] } ) ) > 13
any(False, False, False)

There are many other comparison operations. Some of them might not give you a “wrong” answer, but you might end up doing more processing than you need to do.

Leave a Reply

Your email address will not be published. Required fields are marked *