See also Part 1: Raku and the (Re)blessed Child for a discussion on breaking changes.
The official documentation has this entry for «rebless»:
(Metamodel::Primitives) method rebless
method rebless(Mu $object, Mu $type)
Changes $object to be of type $type . This only
works if $type type-checks against the current type of
$object , and if the storage of $object is a
subset of that of $type .
|
«subset» is a Raku keyword used to set up custom
types (as in e.g.
«subset Positive of Int where * > -1;
» which defines a
type that allows positive integers (including 0) only), but that
doesn't make sense her. So surely it means inheritance and a
subclass.
Here is a very short introduction to classes and subclasses:
File: person-mro
class Person { ; } # [1]
class Woman is Person { ; } # [2]
my $tom = Person.new; # [3]
my $lisa = Woman.new; # [4]
say "Tom belongs to the class { $tom.^name }"; # [5]
say "Tom is { 'not ' unless $tom.isa(Person) }a Person"; # [6]
say "Tom is { 'not ' unless $tom.isa(Woman) }a Woman"; # [6a]
say "Lisa belongs to the class { $lisa.^name }"; # [7]
say "Lisa is { 'not ' unless $lisa.isa(Person) }a Person"; # [8]
say "Lisa is { 'not ' unless $lisa.isa(Woman) }a Woman"; # [8a]
say "Person MRO:", Person.^mro; # [9]
say "Woman MRO:", Woman.^mro; # [10]
Running it:
$ person-mro
Tom belongs to the class Person # [5]
Tom is a Person # [6]
Tom is not a Woman # [6a]
Lisa belongs to the class Woman # [7]
Lisa is a Person # [8]
Lisa is a Woman # [8a]
Person MRO:((Person) (Any) (Mu)) # [9]
Woman MRO:((Woman) (Person) (Any) (Mu)) # [10]
[1] A «Person» class, with no content (the { ; }
part).
[2] A «Woman» class (or subclass) that inherits from «Person» (with the «is» keyword). Also empty.
[3] An object of the «Person» class.
[4] An object of the «Woman» class.
[5] The «^name
» method gives us the class name for the
«$tom
» object, which is «Person».
[6] The «$tom
» object belongs to the «Person» class,
and not to the «Woman» class [6a] (obviously).
[7] As [5], but «$lisa
» and «Woman».
[8] The «$lisa
» object belongs to the «Person» class,
as well as the «Woman» class [8a]. This is a result of the inhertance.
[9] The inherticance tree for the «Person» class. The «Any» and «Mu» classes are built-in, but you can ignore them. «mro» stands for «method resolution order». The search starts from the left.
[10] Note that «Woman» has been added before «Person», so a «Woman» is also a «Person». (And I think that I'll have to find another example, before becoming politically incorrect.)
class Person { ; }
class Woman is Person { ; }
my $tom = Person.new;
my $lisa = Woman.new;
say $tom.^name; # -> Person
say $lisa.^name; # -> Woman
Metamodel::Primitives.rebless($tom, Woman);
say $tom.^name; # -> Woman
This subclass approach worked until march 2019, but not any more:
$ raku rebless-error
Person
Woman
New type Woman for Person is not a mixin type
The error message mentions «mixin type», which is something we get with a «role» that we add (or mix in) to an object.
We want a class name (or rather, something that looks and behaves like a class name), and a little trickery is required to make that work:
File: rebless
class Person { ; } # [1]
constant Woman = Person but role { ; } # [2]
Woman.^set_name('Woman'); # [3]
my $tom = Person.new;
my $lisa = Woman.new;
say $tom.^name; # -> Person
say $lisa.^name; # -> Woman
Metamodel::Primitives.rebless($tom, Woman); # [4]
say $tom.^name; # -> Woman
[1] As before.
[2] Instead of a subclass, we mix in a role (with «but»). «constant» gives us a read-only variable.
[3] This one ensures that the new type reports its name as «Woman».
If we skip this line, we get something like
«Person+{<anon|1>}
» instead.
[4] This works now, as we have used a mixin, and not a subclass.
Running ut:
$ raku rebless
Person
Woman
Woman
But what if we have a circular reference, so that we refer to something that hasn't been defined yet?
We can do it with procedures, without problems:
File: forward-definition
do-something;
sub do-something
{
say "Whatever...";
}
Running it:
$ raku forward-definition
Whatever...
But it does not work with variables or classes:
say $a;
my $a = 12;
class Child { has Parent $.parent; }
class Parent { has Child $.child; }
The solution to the class problem is a Stubbed Class, that we redefine before it is used:
File: stubbed-class
class Parent { ... }
class Child { has Parent $.parent; }
class Parent { has Child $.child; }
That does not work for mixins, but if we store the class-like definition (the mixin) in a variable (instead of declaring it «constant» as we did in the «rebless» program), we can define it up front, and change the value afterwards.
The follwing example has two classes «Child» and «Adult», and the «Child» object changes to an «Adult» object when it reaches 18 years of age.
File: vote
my $Adult; # [1]
class Child # [2]
{
has Int $.age is rw = 0; # [3]
method happy-birthday # [4]
{
$.age++; # [4a]
Metamodel::Primitives.rebless(self, $Adult) if $.age == 18; # [4b]
}
method can-vote # [5]
{
False;
}
}
$Adult = Child but role { method can-vote { True } } # [6]
$Adult.^set_name('Adult'); # [7]
my $tom = Child.new; # [8]
say "Age Can-Vote Class";
for ^20 # [9]
{
say "{ $tom.age.fmt('%3d') } { $tom.can-vote } { $tom.^name }";
$tom.happy-birthday;
}
[1] We store the classlike thing in this variable. For now, we declare it so that [4b] doesn't fail
[2] The Child class,
[3] with the age (in years). The field is set up as read write («rw»), so is not protected in any way. That is a security issue, but that is outside of the scope of this article.
[4] with a method «happy-birtday» that increases the age with one year. When the age reaches 18, the class is changed to «$Adult» [4b]
[5] with a method «can-vote» that returns «False».
[6] Then we set up the classlike thing. Note the role content that overrides the «can-vote» method.
[7] Fix the name of the classlike thing.
[8] Start the show.
[9] Run a loop 20 times, and see what happens when the child reaches 18 years of age.
Running it:
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
18 True Adult
19 True Adult
The fact that «rebless» is not
available on the object itself (e.g.
«$object.rebless(New-Class)
», but through the convoluted
«Metamodel::Primitives.rebless($object, New-Class)
» syntax
should give a hint that this is something that probably isn't meant
for everyday usage.
See also Part 1: Raku and the (Re)blessed Child for a discussion on breaking changes.