Raku rebless and multiple classes
TL;DR I describe several issues. I show a solution at the end that compiles and runs on a recent (2020) Rakudo. It's a simple variant of your own code but I am not knowledgeable enough to vouch for its correctness let alone appropriateness[1] [2].
Cannot change the type of a Any type object
The error message comes from the rebless
line:
Metamodel::Primitives.rebless($, Adult) if $.age == 18;
A $
as a term[3] does not mean self
but instead an anonymous state Scalar
variable. By default it contains an Any
, hence the error message. It should be self
.[4]
Having fixed this first problem, we get a new one depending on which Rakudo version is used:
Older Rakudo:
Incompatible MROs in P6opaque rebless for types Child and Adult
.Newer Rakudo:
New type Adult for Child is not a mixin type
.
Like the first error message we just fixed, these two are also triggered by the rebless
statement.[5]
We must solve both problems.
In a newer Rakudo, fixing the Cannot change the type of a Any type object
and the not a mixin type
problems aren't enough if I use your "adding a new mixin" code; I just get the Incompatible MROs
error.
Conversely, using alternate code that fixes the Incompatible MROs
problem on an older Rakudo leads to the not a mixin type
unless that problem is properly addressed. (In my original version of this answer I solved the Incompatible MROs
problem -- and then neglected to test on a newer Rakudo!)
Your diagnosis of the Incompatible MROs
error was "This one obviously fails, as Adult
is a mixin on Person
, and not on Child
". I read that, glanced at the code, believed you, and moved on. But then I'd arrived back at the same problem using code you'd written to try address it. What gives?
Based on my experiments, it seems that not only must the "to" class (whose class is to be the new class of the object being reblessed) have an MRO that's compatible with the object being reblessed according to things I would expect (like class inheritance) but also the "from" object (the one being reblessed) cannot be both:
Based on a class that has attributes.
Already mixed into.
(I don't know if this is a bug that can be fixed or an unavoidable restriction. I do know a recent (2020) Rakudo has this constraint using both variations of the code Jonathan provided in the previous SO.)
This means that "adding a new mixin to avoid the circular reference problem" ("stubbing and inheritance doesn't like each other") doesn't solve your problem.
Instead, I went back to your "just one class and one mixin" attempt (which ended up with Illegally post-declared type
in the form you originally wrote it) and tried another approach to get around that error.
The following variant of your "just one class and one mixin" code works on a Rakudo v2020.01.114.gcfe.2.cdc.56. All I've done is turned the Adult
constant into a variable. I've written ...
for the rest of the code which is the same as your code:
my $Adult;
...
Metamodel::Primitives.rebless(self, $Adult) if $.age == 18;
...
$Adult = Child but role { method can-vote { True } }
$Adult.^set_name('Adult');
...
Hth.
Footnotes
[1] Jonathan's solution in a recent SO used compile-time constructs for Adult
. My solution follows Jonathan's example except that it constructs the rebless target $Adult
at run-time. I'm unsure if this is technically safe in the face of the new optimization @JonathanWorthington has introduced. I will try to "summon" him to comment on it.
[2] Other than this footnote, my answer does not address the wisdom of using rebless
. Two issues come immediately to mind for me. First is reliable functionality given turophilia, which is clearly central to you even needing to ask your recent SOs. (And with it, metaturophilia. That is, we currently have holes in our approach to maturing Raku, the language, and Rakudo, the implementation. To the degree code written by one of us leads to holes being filled in we can all be grateful.) Second is reliable documentation of the MOP given that (as far as I can tell) some key documentation breaks the general rule of constraining itself to the Raku specification according to roast and instead "largely reflects the metaobject system as implemented by the Rakudo Raku compiler". I just address errors until your code compiles and runs without error on a 2020 version of Rakudo.
[3] See What is a term? as linked to with some context in this comment.
[4] Some folk might presume that if $.foo
is a .foo
of self
, then $
must be self
. Such thinking would be a reasonable presumption if raku had the typical context-free tokenization used for most programming languages. Moreover, it generally applies to Raku code too, just as it generally applies even in natural language. (If the English token "my" is followed by "self", then it likely means the same as "myself".) But Raku's grammar deliberately combines context sensitivity, scannerless parsing and maximal munch in order to support creation of more natural feeling languages than is typical for programming languages. And here we see an example. In "term position"[3] the input $.foo
is recognized as a single token instead of two ($
followed by .foo
) whereas the input $,...
is recognized as two tokens ($
followed by the list separator operator ,
) rather than one.
[5] All of these error messages are generated in parts of Rakudo that are close to the metal. If you're using MoarVM as your backend they come from its P6opaque.c file.