(This is a follow up to: Raku rebless doesn't work with inherited classes anymore)
I have tried to come up with a more complex use case, but am unable to get the code to work.
The idea is a Person class, with mixin subclasses for Child and Adult. We have a Child object, and change the type to Adult when the age passes 18 year.
This one obviously fails, as Adult is a mixin on Parent, and not on Child:
class Person
{
has Int $.age is rw = 0;
method happy-birthday
{
$.age++;
# Metamodel::Primitives.rebless($, Adult) if $.age == 18;
}
method can-vote
{
...;
}
}
constant Adult = Person but role { method can-vote { True } }
constant Child = Person but role
{
method can-vote { False }
method happy-birthday
{
$.age++;
Metamodel::Primitives.rebless(self, Adult) if $.age == 18;
}
}
BEGIN Child.^set_name('Child');
BEGIN Adult.^set_name('Adult');
my $tom = Child.new;
say "Age Can-Vote Class";
for ^20
{
say "{ $tom.age.fmt('%3d') } { $tom.can-vote } { $tom.^name }";
$tom.happy-birthday;
}
But it runs partially:
Age Can-Vote Class
0 False Child
1 False Child
2 False Child
3 False Child
4 False Child
5 False Child
6 False Child
7 False Child
8 False Child
9 False Child
10 False Child
11 False Child
12 False Child
13 False Child
14 False Child
15 False Child
16 False Child
17 False Child
Incompatible MROs in P6opaque rebless for types Child and Adult
in method happy-birthday at ./vote-error line 28
Setting it up with just one class and one mixin is the thing:
class Child
{
has Int $.age is rw = 0;
method happy-birthday
{
$.age++;
Metamodel::Primitives.rebless($, Adult) if $.age == 18;
}
method can-vote
{
False;
}
}
constant Adult = Child but role { method can-vote { True } }
BEGIN Adult.^set_name('Adult');
my $tom = Child.new;
say "Age Can-Vote Class";
for ^20
{
say "{ $tom.age.fmt('%3d') } { $tom.can-vote } { $tom.^name }";
$tom.happy-birthday;
}
Except that it doesn't work:
Error while compiling vote-error1
Illegally post-declared type:
Adult used at line 10
I get that. The rebless line refers to Adult, which hasn't been declared yet. So I tried stubbing the class:
class Child { ... }
constant Adult = Child but role { method can-vote { True } }
class Child
{
has Int $.age is rw = 0;
method happy-birthday
{
$.age++;
Metamodel::Primitives.rebless($, Adult) if $.age == 18;
}
method can-vote
{
False;
}
}
BEGIN Adult.^set_name('Adult');
my $tom = Child.new;
say "Age Can-Vote Class";
for ^20
{
say "{ $tom.age.fmt('%3d') } { $tom.can-vote } { $tom.^name }";
$tom.happy-birthday;
}
But stubbing and inheritance doesn't like each other:
===SORRY!=== Error while compiling vote-error2
'Child+{<anon|1>}' cannot inherit from 'Child' because 'Child' isn't composed yet (maybe it is stubbed)
Then I tried adding a new mixin to avoid the circular reference problem:
class Child
{
has Int $.age is rw = 0;
method can-vote
{
False;
}
}
constant Adult = Child but role { method can-vote { True } }
BEGIN Adult.^set_name('Adult');
role still-a-child
{
method happy-birthday
{
$.age++;
Metamodel::Primitives.rebless($, Adult) if $.age == 18;
}
}
my $tom = Child.new but still-a-child;
say "Age Can-Vote Class";
for ^20
{
say "{ $tom.age.fmt('%3d') } { $tom.can-vote } { $tom.^name }";
$tom.happy-birthday;
}
But that failed as well:
Age Can-Vote Class
0 False Child+{still-a-child}
1 False Child+{still-a-child}
2 False Child+{still-a-child}
3 False Child+{still-a-child}
4 False Child+{still-a-child}
5 False Child+{still-a-child}
6 False Child+{still-a-child}
7 False Child+{still-a-child}
8 False Child+{still-a-child}
9 False Child+{still-a-child}
10 False Child+{still-a-child}
11 False Child+{still-a-child}
12 False Child+{still-a-child}
13 False Child+{still-a-child}
14 False Child+{still-a-child}
15 False Child+{still-a-child}
16 False Child+{still-a-child}
17 False Child+{still-a-child}
Cannot change the type of a Any type object
in method happy-birthday at vote-error3 line 26
And it did as $tom is now something else than a Child, and Adult isn't a mixin of what we now have. But the error message isn't very helpful.
The last one is essentially the same as the first one.
And I am stuck.
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.
[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.
Thank you. But I am unable to make it work:
class Adult { ... }
class Child
{
has Int $.age is rw = 0;
method happy-birthday
{
$.age++;
Metamodel::Primitives.rebless(self, Adult) if $.age == 18;
}
method can-vote
{
False;
}
}
role grown-up { method can-vote { True } }
class Adult is Child does grown-up { }
my $tom = Child.new;
say "Age Can-Vote Class";
for ^20
{
say "{ $tom.age.fmt('%3d') } { $tom.can-vote } { $tom.^name }";
$tom.happy-birthday;
}
This runs to 17, then fails with
New type Adult for Child is not a mixin type
What am I missing?
(And Stackowerflow should allow code in comments.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With