Exploring Rebless with Raku

[57] Published 12. February 2020

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:

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]

File: person-mro

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.)

Rebless, First Try

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

Let us have a go at it: File: rebless-error

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:

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

File: rebless

[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:

do-something; sub do-something { say "Whatever..."; }

File: forward-definition

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:

class Parent { ... } class Child { has Parent $.parent; } class Parent { has Child $.child; }

File: stubbed-class

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.

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; }

File: vote

[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

Last Words

The «rebless» examples are just meant to illustrate the point. Both programs would be better off with another approach.

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.