Reputation: 132820
This is a reported bug in Perl 6: X::AdHoc instead of X::TypeCheck::Binding with subset parameter, first reported in November 2015.
While playing with my Perl 6 module Chemisty::Elements, I've run into an Exception issue I didn't expect.
I define a type, ZInt
, which limits numbers to the ordinal numbers found on the periodic chart (which I've faked a bit here). I then use that type to constrain a parameter to a subroutine. I expected to get some sort of X::TypeCheck, but I get X::AdHoc instead:
use v6;
subset ZInt of Cool is export where {
state ( $min, $max ) = <1 120>;
( $_.truncate == $_ and $min <= $_ <= $max )
or warn "Z must be between a positive whole number from $min to $max. Got <$_>."
};
sub foo ( ZInt $Z ) { say $Z }
try {
CATCH {
default { .^name.say }
}
foo( 156 );
}
First, I get the warning twice, which is weird:
Z must be between a positive whole number from 1 to 120. Got <156>. in block at zint.p6 line 5 Z must be between a positive whole number from 1 to 120. Got <156>. in block at zint.p6 line 5 X::AdHoc
But, I get the X::AdHoc
type when I'd rather people knew it was a type error.
I checked what would happen without the warn
and got X::AdHoc
again:
subset ZInt of Cool is export where {
state ( $min, $max ) = <1 120>;
( $_.truncate == $_ and $min <= $_ <= $max )
};
So, I figured I could throw my own exception:
subset ZInt of Cool is export where {
state ( $min, $max ) = <1 120>;
( $_.truncate == $_ and $min <= $_ <= $max )
or X::TypeCheck.new.throw;
};
But, I get a warning:
Use of uninitialized value of type Any in string context Any of .^name, .perl, .gist, or .say can stringify undefined things, if needed.
At this point I don't know what's complaining. I figure one of those methods expects something I'm not supplying but I don't see anything about parameters for new
or throw
in the docs.
How do I get the type I want without the warning, along with my custom text?
Upvotes: 8
Views: 202
Reputation: 3995
Don't throw the exception or warn with one. Instead, you want to fail:
subset ZInt of Cool is export where {
state ( $min, $max ) = <1 120>;
( $_.truncate == $_ and $min <= $_ <= $max )
or fail "Z must be between a positive whole number from $min to $max. Got <$_>."
};
I believe that's your intent. Failing with your own exception is fine too, but X::TypeCheck has a bug in it. It should either require "operation" or provide a reasonable default as it does for "got" and "expected".
subset ZInt of Cool is export where {
state ( $min, $max ) = <1 120>;
( $_.truncate == $_ and $min <= $_ <= $max )
or fail X::TypeCheck.new(
operation => "type check",
expected => ::('ZInt'),
got => $_,
);
};
Upvotes: 3
Reputation: 169633
You could pass --ll-exception
and try to figure out how exactly you end up with the errors and messages you got, but I'm not sure how helpful that will be.
As to the warning about use of an uninitialzed value: You need to provide a named operation
argument to X::TypeCheck.new
; other arguments you may provide are got
and expected
, cf core/Exception.pm.
It is however a Bad Idea to throw from a subset declaration as any smartmatch against that particular type will now explode. A slightly better idea would be to .fail
the exception, but that still doesn't feel right to me: Not being a member of a subset type is not an exceptional condition.
Alternatively, you could provide a multi candidate that does the dying:
subset ZInt of Cool where $_ %% 1 && $_ ~~ 1..120;
proto foo($) {*}
multi foo(ZInt $Z) { say $Z }
multi foo($Z) {
die X::TypeCheck.new(
operation => 'foo',
got => $Z,
expected => ZInt
);
}
That still has issues if you provide an argument like "hello"
that fails on numeric conversion as %%
will throw instead of propagating the failure, which could be considered a defect with the Rakudo core setting.
You can work around that one via things like
subset ZInt of Cool where { try $_ %% 1 && $_ ~~ 1..120 }
or
subset ZInt of Cool where { .Numeric andthen $_ %% 1 && $_ ~~ 1..120 }
The whole interaction of argument type checking, subsets or where-clauses, failures and exceptions can be somewhat brittle, so you may want to experiment a bit until you arrive at semantics and behaviour you like.
Another approach would be doing a coercion from Cool
to Int
with a separate range check:
subset ZInt of Int where 1..120 ;
sub foo(Int(Cool) $Z where ZInt) {
say $Z.perl;
}
In an ideal world, there should be some way to express this with a coercing type constraint like ZInt(Cool)
.
Upvotes: 3