type constraint :D
Documentation for type constraint
:D assembled from the following types:
Normally, a type constraint only checks whether the value of the parameter is of the correct type. Crucially, both object instances and type objects will satisfy such a constraint as illustrated below:
say 42.^name; # OUTPUT: «Int␤»say 42 ~~ Int; # OUTPUT: «True␤»say Int ~~ Int; # OUTPUT: «True␤»
Note how both
Int satisfy the match.
Sometimes we need to distinguish between these object instances (
42) and type objects (
Int). Consider the following code:
sub limit-lines(Str , Int )say (limit-lines "a \n b \n c \n d \n", 3).perl; # "a \n b \n c \n d "say limit-lines Str, 3;CATCH ;# OUTPUT: «X::Multi::NoMatch: Cannot resolve caller lines(Str: ); none of these signatures match:# (Str:D $: :$count!, *%_)# (Str:D $: $limit, *%_)# (Str:D $: *%_)»say limit-lines "a \n b", Int # Always returns the max number of lines
Here we really only want to deal with string instances, not type objects. To do this, we can use the
:D type constraint. This constraint checks that the value passed is an object instance, in a similar fashion to calling its DEFINITE method.
To warm up, let's apply
:D to the right hand side of our humble
say 42 ~~ Int; # OUTPUT: «True␤»say Int ~~ Int; # OUTPUT: «False␤»
Note how only
Int:D in the above.
limit-lines, we can now amend its signature to catch the error early:
sub limit-lines(Str , Int ) ;say limit-lines Str, 3;CATCH ;# OUTPUT: «Parameter '$s' of routine 'limit-lines' must be an object instance of type 'Str',# not a type object of type 'Str'. Did you forget a '.new'?»
This is much better than the way the program failed before, since here the reason for failure is clearer.
It's also possible that type objects are the only ones that make sense for a routine to accept. This can be done with the
:U type constraint, which checks whether the value passed is a type object rather than an object instance. Here's our
Int example again, this time with
say 42 ~~ Int; # OUTPUT: «False␤»say Int ~~ Int; # OUTPUT: «True␤»
42 fails to match
Here's a more practical example:
sub can-turn-into(Str , Any )say can-turn-into("3", Int);say can-turn-into("6.5", Int);say can-turn-into("6.5", Num);say can-turn-into("a string", Num);# OUTPUT: True True True False
can-turn-into with an object instance as its second parameter will yield a constraint violation as intended:
say can-turn-into("a string", 123);# OUTPUT: «Parameter '$type' of routine 'can-turn-into' must be a type object of type 'Any', not an object instance of type 'Int'...»
For explicitly indicating the normal behaviour,
:_ can be used, but this is unnecessary.
:(Num:_ $) is the same as
To recap, here is a quick illustration of these type constraints, also known collectively as type smileys:
# Checking a type objectsay Int ~~ Any; # OUTPUT: «False␤»say Int ~~ Any; # OUTPUT: «True␤»say Int ~~ Any; # OUTPUT: «True␤»# Checking an object instancesay 42 ~~ Any; # OUTPUT: «True␤»say 42 ~~ Any; # OUTPUT: «False␤»say 42 ~~ Any; # OUTPUT: «True␤»# Checking a user-supplied class;say Foo ~~ Any; # OUTPUT: «False␤»say Foo ~~ Any; # OUTPUT: «True␤»say Foo ~~ Any; # OUTPUT: «True␤»my = Foo.new;say ~~ Any; # OUTPUT: «True␤»say ~~ Any; # OUTPUT: «False␤»say ~~ Any; # OUTPUT: «True␤»
The Classes and Objects document further elaborates on the concepts of instances and type objects and discovering them with the
Keep in mind all parameters have values; even optional ones have default defaults that are the type object of the constrained type for explicit type constraints. If no explicit type constraint exists, the default default is an Any type object for methods, submethods, and subroutines, and a Mu type object for blocks. This means that if you use the
:D type smiley, you'd need to provide a default value or make the parameter required. Otherwise, the default default would be a type object, which would fail the definiteness constraint.
sub divide (Int : = 2, Int :!)divide :1a, :2b; # OUTPUT: «0.5␤»