type constraint :D

Documentation for type constraint :D assembled from the following types:

class Signature

From Signature

(Signature) type constraint :D

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 42 and 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 $sInt $limit{
    my @lines = $s.lines;
    @lines[0 .. min @lines.elems$limit].join("\n")
}
say (limit-lines "\n b \n c \n d \n"3).perl# "a \n b \n c \n d " 
say limit-lines Str3;
CATCH { default { put .^name''.Str } };
# 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 "\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 Int example:

say  42 ~~ Int:D;  # OUTPUT: «True␤» 
say Int ~~ Int:D;  # OUTPUT: «False␤» 

Note how only 42 matches Int:D in the above.

Returning to limit-lines, we can now amend its signature to catch the error early:

sub limit-lines(Str:D $sInt $limit{ };
say limit-lines Str3;
CATCH { default { put .^name ~ '--' ~ .Str } };
# 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 :U applied:

say  42 ~~ Int:U;  # OUTPUT: «False␤» 
say Int ~~ Int:U;  # OUTPUT: «True␤» 

Now 42 fails to match Int:U while Int succeeds.

Here's a more practical example:

sub can-turn-into(Str $stringAny:U $type{
   return so $string.$type;
}
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 

Calling 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 :(Num $).

To recap, here is a quick illustration of these type constraints, also known collectively as type smileys:

# Checking a type object 
say Int ~~ Any:D;    # OUTPUT: «False␤» 
say Int ~~ Any:U;    # OUTPUT: «True␤» 
say Int ~~ Any:_;    # OUTPUT: «True␤» 
 
# Checking an object instance 
say 42 ~~ Any:D;     # OUTPUT: «True␤» 
say 42 ~~ Any:U;     # OUTPUT: «False␤» 
say 42 ~~ Any:_;     # OUTPUT: «True␤» 
 
# Checking a user-supplied class 
class Foo {};
say Foo ~~ Any:D;    # OUTPUT: «False␤» 
say Foo ~~ Any:U;    # OUTPUT: «True␤» 
say Foo ~~ Any:_;    # OUTPUT: «True␤» 
my $f = Foo.new;
say $f  ~~ Any:D;    # OUTPUT: «True␤» 
say $f  ~~ Any:U;    # OUTPUT: «False␤» 
say $f  ~~ Any:_;    # OUTPUT: «True␤» 

The Classes and Objects document further elaborates on the concepts of instances and type objects and discovering them with the .DEFINITE method.

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:D :$a = 2Int:D :$b!{ say $a/$b }
divide :1a, :2b; # OUTPUT: «0.5␤»