Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl6: .sort() doesn't use overridden cmp

Tags:

raku

According to the documentation, sort compares using infix:<cmp>.

But:

class Point
{
    has Int $.x;
    has Int $.y;
    method Str { "($!x,$!y)" }
    method gist { self.Str }
}
multi sub infix:<cmp>(Point $a, Point $b) { $a.y cmp $b.y || $a.x cmp $b.x }

my @p = Point.new(:3x, :2y), Point.new(:2x, :4y), Point.new(:1x, :1y);
say @p.sort;

gives output:

((1,1) (2,4) (3,2))

When I use:

say @p.sort(&infix:<cmp>);

it does give the proper output:

((1,1) (3,2) (2,4))

Is this a bug, a feature, or a flaw in the documentation? And is there a way to make .sort() on a list of a Points use a custom sort order without specifying a routine?

like image 960
mscha Avatar asked Dec 13 '18 11:12

mscha


2 Answers

I think that's a case of Broken By Design. Consider the following snippet:

my $a = Point.new(:3x, :2y);
my $b = Point.new(:2x, :4y);

say &infix:<cmp>.WHICH;
say $a cmp $b;

{
    multi sub infix:<cmp>(Point $a, Point $b) { $a.y cmp $b.y || $a.x cmp $b.x }
    say &infix:<cmp>.WHICH;
    say $a cmp $b;
}

say &infix:<cmp>.WHICH;
say $a cmp $b;

The definition of the new multi candidate will generate a new proto sub that is only visible lexically. As the sort method is defined in the setting (conceptionally, an enclosing scope), it won't see your new multi candidate.

It might be possible to make sort look up &infix:<cmp> dynamically instead of lexically, though I suspect such a change would have to wait for 6.e even if we decided that's something we want to do, which isn't a given.

As a workaround, you could do something like

constant CMP = &infix:<cmp>;
multi sub infix:<cmp>(Point $a, Point $b) { ... }
BEGIN CMP.wrap(&infix:<cmp>);

for now, though I wouldn't necessarily recommend it (messing with global state considered harmful, and all that jazz)...

like image 151
Christoph Avatar answered Oct 28 '22 16:10

Christoph


The cmp that is being used is the cmp that is in lexical scope within sort, not the one you have defined. If you change a few lines to:

multi sub infix:<cmp>(Point $a, Point $b) { 
  say "Hey $a $b";
  $a.y cmp $b.y || $a.x cmp $b.x 
}
my @p = Point.new(:3x, :2y), Point.new(:2x, :4y), Point.new(:1x, :1y);
say @p.sort( { $^b cmp $^a } );

Since the cmp that is being used is the one that is in actual lexical scope, it gets called correctly printing:

Hey (2,4) (3,2) Hey (1,1) (2,4) Hey (1,1) (3,2) ((2,4) (3,2) (1,1))

As was required.

like image 3
jjmerelo Avatar answered Oct 28 '22 17:10

jjmerelo