subset name of Any where Str|True: why does the name type match every type?

130 views Asked by At

I expected the refinement of the Any type to Str|True would make the name type match any Str or True, but this is what I see:

subset name of Any where Str|True;

sub go(name :$x) {
   say $x;
}

go(x => "hello");  #hello
go(x => True);     #True

go(x => 2);        #2
my @arr = 1, 2, 3; 
go(x => @arr);     #[1, 2, 3]

If I change the subset to subset name of Any where Str|Bool, then it works as I expect:

subset name of Any where Str|Bool;

sub go(name :$x) {
   say $x;
}

go(x => "hello");   
go(x => True);      
go(x => False);

go(x => 2);

--output:--
hello
True
False
Constraint type check failed in binding to parameter '$x'; expected name but got Int (2)
  in sub go at b.raku line 19
  in block <unit> at b.raku line 27

The subset name of Any where Str|True; was in the docs for MAIN, which is the first place I encountered a subset. So, the rule seems to be that you must declare types rather than instances of a type when refining a type with a subset. But, then I tried:

subset name of Any where Str|2;

sub go(name :$x) {
   say $x;
}

go(x => "hello");
go(x => 2);
go(x => 6);

--output:--
hello
2
Constraint type check failed in binding to parameter '$x'; expected name but got Int (6)
  in sub go at b.raku line 19
  in block <unit> at b.raku line 25

Hmmm...the following seems to be the problem with True:

[197] > @arr ~~ True
Potential difficulties:
    Smartmatch against True always matches; if you mean to test the topic for truthiness, use :so or *.so or ?* instead
    ------> @arr ~~ ⏏True
True

[198] > 7 ~~ True
Potential difficulties:
    Smartmatch against True always matches; if you mean to test the topic for truthiness, use :so or *.so or ?* instead
    ------> 7 ~~ ⏏True
True

[199] > False ~~ True
Potential difficulties:
    Smartmatch against True always matches; if you mean to test the topic for truthiness, use :so or *.so or ?* instead
    ------> False ~~ ⏏True
True

Why would False match True?

Okay, according to the docs, the smartmatch operator aliases the lhs to $_, then calls rhs.ACCEPTS($_), and for the Bool class ACCEPTS is defined to return the invocant. Therefore, if True is on the rhs side of the smartmatch operator, ACCEPTS always returns True, and if False is on the rhs of the smartmatch operater, ACCEPTS always returns False, leading to this strange result:

[203] > False ~~ False
Potential difficulties:
    Smartmatch against False always fails; if you mean to test the topic for truthiness, use :!so or *.not or !* instead
    ------> False ~~ ⏏False
False
 

The result is that False smartmatches True, but False does not smartmatch False???

2

There are 2 answers

3
hobbs On

That's how e.g. you can write any normal test expression in a given/when and have it work; an expr that evaluates to a Bool will be used as the result of smartmatch, ignoring the "subject" of the match. Pretty expected behavior.

As for how to write the type you want, how about the plain vanilla:

subset name of Any where { $_ ~~ Str || $_ ~~ Bool && ?$_ }

7
jjmerelo On

What @hobbs says is essentially true; however I'd maybe like to explain the sentence:

an expr that evaluates to a Bool will be used as the result of smartmatch, ignoring the "subject" of the match. Pretty expected behavior.

That is, Str | True is a Junction equivalent to any((Str), True). You can use a Junction for smartmatching, obviously, but its behavior will be:

and then uses smartmatching to check whether [it] matches any of them

So what you have written above:

say Array ~~ Str | True; # Will return True
say <1 2 3> ~~ Str | True; # Yup, still True
say False ~~ Str | True; # Ditto

Any Junction like the one you have used to declare your subset with always return True, anything you match it with.