definedness constraint on parametrized roles

195 Views Asked by At

Should it be possible to add a definedness constraint on parameterized roles? The following attempt:

role R[::T] { }
class C {
    method m ( R[Str]:D $a ) { }
}

results in:

Invalid typename 'D' in parameter declaration.
at ... scratch.raku:4
------> method m ( R[Str]:D⏏ $a ) { }

and declaring a variable:

role R[::T] { }
class S does R[Str] { }
my R[Str]:D $r = S.new;

results in:

Malformed my
at ... scratch.raku:8
------> my R[Str]⏏:D $r = S.new;

2

There are 2 best solutions below

0
raiph On

TL;DR @wamba's answer assumes the "definiteness" interpretation of your question (which seems likely to be what you actually meant) and provides a nice succinct solution for that. This answer explains why your question is ambiguous, and provides a solution for both interpretations. Neither of my solutions are @wamba's, so this answer serves as a complement to theirs.

Your question is ambiguous

Here's what you wrote at the start:

Should it be possible to add a definedness constraint on parameterized roles?

Here instead is the implicit question:

Should it be possible to add a definiteness constraint on parameterized roles?

I'll address them in reverse order, given that it's near certain you meant definite, not defined.

Definite

Definiteness corresponds to the :D type constraint or .DEFINITE compiler macro.

This is what the code in your question is about.

The fact that R[Str]:D is invalid syntax may be due to an oversight, or lack of tuits, or lack of consensus, or a deliberate decision. I have not researched that. Someone may have filed an issue about it.¹ Again, I have not researched that.

Here's a way to add an equivalent definiteness constraint that works for your example:

role R[::T] { }
class C {
    method m ( R[Str] $a where .DEFINITE) { 42 }
}

say C.new.m: R[Str].new; # 42
say C.new.m: R[Str];     # Constraint type check failed in binding to parameter '$a'

Defined

Definedness corresponds to the .defined method call, as tested by features such as with and infix //.

This is what your question's title and opening sentence refer to, taken literally.

Should it be possible to add a definedness constraint on parameterized roles?

Here's one way:

role R[::T] { }
class C {
    method m ( R[Str] $a where .defined) { 42 }
}

say C.new.m: R[Str].new; # 42
say C.new.m: R[Str];     # Constraint type check failed in binding to parameter '$a'

Note how the result is the same as for .DEFINITE (and would have been if Type[Type]:D was valid syntax).

But they aren't always the same for all objects, which reflects the fact that a definiteness constraint is not exactly the same as a definedness constraint -- :D and .DEFINITE correspond to definiteness whereas .defined corresponds to definedness.

Definite vs Defined

The similarity between definiteness and definedness is deliberate. If a given object has not got a .defined method (and almost none do) it will inherit Mu's. And that .defined method just returns self.DEFINITE.

But while the DEFINITE method cannot be overridden², the .defined one can be:

  1. Users may override .defined for their classes/objects. This makes it easy for someone writing a foreign language adaptor to allow Raku interop with another PL which has a different notion of definedness than Raku's. (This is pretty much the only use case. It is generally ill-advised for ordinary Raku code to override .defined.)

  2. The built in Failure class overrides .defined. It returns False for both the type object (as normal) and instances. Thus:

say "{.DEFINITE.gist}\t{.defined.gist}\t{.gist}"
  for (Mu, Mu.new, Int, 42, Failure, Failure.new)

displays:

False False   (Mu)
True  True    Mu.new
False False   (Int)
True  True    42
False False   (Failure)
True  False   (HANDLED) Failed

The last line, which corresponds to Failure.new, has True in the first column (its .DEFINITE is True) but False in the second column (its .defined is False).

Footnotes

¹ I don't think an issue should be filed for making Type[Type]:D work. Similarly, if an issue has been filed, then I would anticipate it being ignored for the foreseeable future unless a non-core-dev creates a suitable PR for it (which may still be rejected).

² Definiteness is fundamental to the static typing foundation of Raku's gradual type system and its notion that an "object" is something that can be either a type object or an object instance. To allow the compiler to rely on this simple either/or boolean at compile time, the behavior of "definiteness", and thus the .DEFINITE macro, cannot be overridden by user code -- unlike the .defined method.

2
wamba On

It is possible add a definiteness constraint on parameterized roles using R:D[Str] syntax.

So, your example would look like this:

role R[::T] { }
class S does R[Str] { }
my R:D[Str] $r = S.new;

However keep in mind that $r could be definite yet undefined (see @raiph's answer):

role R[::T] { method defined { False} }
  class S does R[Str] { }
  my R:D[Str] $r = S.new; 
  say $r // 'undefined';