RFC 137: Perl OO should not be fundamentally changed.

Now, as you have read the title and already stopped laughing… Er, not all of you yet? Ok, I’ll give you another minute…

Good, let’s be serious now. RFC 137 was written by Damian Conway. Yes, the title too. No, I’m serious! Check it yourself! And then also read other RFCs from language-objects category. Turns out, it was the common intention back then: don’t break things beyond necessary, keep everything as backward compatible as possible.

A familiar stance, isn’t it?

I chose this RFC not over its title but because it might be considered as the source of the river we now call the Raku OO model.

Let’s look closer into the RFC text. To be frank, this gonna be second time only as I read it. Gosh, three weeks ago I even had no idea Perl6 started with RFCs! My long-time belief was the synopses were the first to come. And since even they are now pretty much outdated in many places, studying RFCs is like studying the first stone tools of Homo Habilis: there is not much in common with what we use nowadays, but how great is it to see ways human mind adapt and improve its own ideas! So, let’s get back into our archeological excavation and start examining our sample.

The first prominent feature we find states that:

It ain’t broken. Don’t fix it.

Here and below all quotes are from the RFC body.

And you know what? Back then it was my consideration too! All I really wanted was class and method keywords, private declarations… And that’s basically all. Perl with classes, what else?

Heh, young and stupid, aha…

The way I see it now? It’s still not broken. But neither it is good. And once you try it in Raku – there is no way back.

Perl’s current OO model has a number of well-known deficiencies: lack of (easy) encapsulation, poor support for hierarchical method calls (especially constructors and destructors), limited (single) dispatch mechanism, poor compile-time checking. More fundamentally, many people find that setting up reliable OO class hierarchies requires too much low-level coding.

Fairly long list, isn’t it? The good thing: these days there is Moose family of toolkits to get solutions for many of the listed problems. Not all of them though. But the fact of existence of these toolkits tells a lot about Perl’s flexibility which was really something outstanding back then, in the late 90s. Even though Moose didn’t exists yet in 2000, there were already a few OO toolkits implementing different approaches.

The bad thing: all of them are external solutions. Yes, CPAN. Yes, easy to install. Still…

This is one of the aspects where Raku shines absolutely: every single issue from the list above has been taken care of. And event some problems beyond the listed ones. Eventually, this is what made Raku almost totally backward incompatible to Perl. But do I feel pity about it? I’m certainly do not!

Later in RFC text Damian writes:

The non-prescriptive, non-proscriptive nature of Perl’s OO model makes it possible to construct am enormous range of OO systems within the one language

And you know what? You can do it in Raku too! Yes, you can create your own OO model from the scratch by utilizing Raku’s powerful meta-object protocol (MOP) capabilities. Apparently I won’t be discussing this matter in this article. But I can give you a hint:

say EXPORTHOW::<class>.^name; # Perl6::Metamodel::ClassHOW

Congratulations! We just found the class which is responsible for Raku class keyword. It is possible to implement your own keyword like, say, myclass (sorry for a commonplace) and make it possible to have declarations like:

myclass Foo { }

And make sure that the behavior of myclass kind of type objects is different from class. How much different is totally up to one’s imagination and demands.

Don’t like it? Create your own slang and extend Raku grammar with it! Why not to have something like:

myclass Foo;
   private Int attr1;
   public Str attr2;
end

It’s possible. The only remark to make about it: the power of Raku makes this kind of tricks unnecessary most of the time.

Yet, the ability to declare own class-like keyword which provides some kind of specialized functionality proves to be extremely useful for creating an ORM with an exceptional level of flexibility and ease of use!

A polite cough from the audience reminds me that it’s time to change subject. My apologies, the MOP is my all-time fad!

Of course, the RFC is not about critics but proposals. Let’s see why I think that this one is the source of many things we have in the Raku language today.

A private keyword that lexically scopes hash keys to the current package

As Perl’s OO wasn’t planned to for a drastic change in v5 to v6 transition, it was supposed to still remain based upon blessed hashes. This is not true for Raku. Why and how are questions not for an article, but rather for a small book. Anyway, the idea of private class members is here, even though not the keyword private itself:

class Foo {
    has $!private;
    has $.public;
    method !only-mine { }
    method for-anyone { self!only-mine }
}
my $foo = Foo.new;
$foo.for-anyone;
$foo.only-mine; # Error
say $foo.public;
say $foo.private; # Error

Instead of over-verbose public/private declarations Raku uses concise twigil notation using . for publics and ! for privates. But what’s even more interesting is that the only difference between the two declared attributes is that $.public automatically receives an accompanying method public which is the accessor for the attribute. Because, as a matter of fact, in Raku all attributes are actually private! By simplifying a bit, it can be said that the only thing which a class exposes into the world is its public methods.

A new special subroutine name — SETUP — to separate construction from initialization.

In fact, Raku’s object construction model is based upon three special methods: BUILDALL, BUILD, and TWEAK. The latter is the evolution of SETUP method idea.

But! (There is often a but in Raku) The three are not the kind of methods we used to think about them. They are submethods which are the sole property of a class or a role which declares them. What it means in practice is:

class Foo {
    submethod foo { say "foo!" } 
    method bar { say "bar!" }
}
class Bar is Foo { }
Foo.foo; # foo!
Bar.bar; # bar!
Bar.foo; # Error: no such method

Submethods are a kind of tool ideally suited for performing tasks totally specific to the class/role. Precisely like the construction/destruction tasks.

  • Changes to the semantics of bless so that, after associating an object with a class, the class’s SETUP methods are automatically called on the object. An additional trailing @ parameter for bless, to allow arguments to be passed to SETUP methods.
  • Changes to the semantics of DESTROY, so that all inherited destructors are, by default, automatically called when an object is destroyed.

Yes and no. Back then, when the RFC was written, the low-level architecture of Perl6 not even started to be discussed. Thus many ideas was based on Perl5 design in which the core is written in C and bundled with a bunch of modules. Correspondingly, bless is the core thing doing some kind of magic to the data provided as an argument. It seemed natural to teach bless a few additional tricks to get the desired outcome.

In Raku everything is different in this area. First of all, the language specification doesn’t imply the exact way of how construction/destruction stages are to be implemented, it only demands the constructor/destructor methods to be supported and invoked in specific order with specific parameters. So, the “Yes” in the beginning of the previous paragraph is related to the fact that the automatic invocation does take place and the arguments are passed from a call to the method new to the construction submethods:

class Foo {
    has $.foo;
    method TWEAK(:$foo) {
        $!foo = $foo * 10 if $foo < 10;
    }
}
say Foo.new(foo => 5).foo; # 50

But the “No” above is related to the fact that there is no low-level bless subroutine responsible for how the things are done. For example, the way Rakudo compiler implements the specification of object construction is basically winds down to something like this incomplete pseudo-code:

method new(*%attrinit) {
    self.bless(|%attrinit)
}
method bless(*%attrinit) {
    nqp::create(self).BUILDALL(Empty, %attrinit)
}

So bless is no more than just an ordinary pre-defined method. If necessary, you can override it in your class:

class Foo {
    has $.foo;
    method bless(:$foo, *%c) {
        nextwith(|%c, foo => $foo * 2) 
    } 
}
say Foo.new(foo => 4).foo; # 8

The code will work because this part of the construction logic is partially implemented by class Mu from which all Raku classes indirectly inherit by default. So, if no special care is taken, when you do Foo.new it means the method new from Mu is invoked.

Besides, in Rakudo’s scenario all the “magic” of object initialization eventually happens within the Mu::BUILDALL method which is using the MOP to determine all the steps to be done for you to get an instance of Foo properly setup and ready for use.

Off all the above steps it’s only the nqp::create call which is served by the low-level core executable (virtual or bytecode machine in Rakudo terms) and which purpose is to allocate and initialize memory for an object representation.

Pre- and post-condition specifiers, which associate code blocks with particular subroutine/method names.

This idea has never got developed. Instead Raku has gotten something way more powerful! A concept which applies not only to routines but to any object. And don’t forget: in Raku everything is an object! The concept I’m talking about is trait.

A trait is a routine which gets invoked at code compilation time and gets the object it is applied to as its argument. It’s also possible to pass your own arguments to the trait, which is able to use the full power of MOP to setup or even alter the object the way the user needs it to. For example:

class Foo {
    has $.foo is rw is default(42);
}

In this snippet is default(42) is a simple example of passing an argument to a trait. The meaning is to specify the value the attribute will get initially and every time it gets assigned with Nil.

is rw makes the attribute writable because, by default, all attributes in Raku are read-only. Remember I wrote earlier that everything is an object? Attributes are no exception! RO or RW status of an attribute is determined by… well, by an attribute value on an Attribute object! Thus, our is rw trait is actually as simple as:

multi sub trait_mod:<is>(Attribute:D $attr, :rw($)!) {
    $attr.set_rw();
    warn "useless use of 'is rw' on $attr.name()" unless $attr.has_accessor;
}

Just ignore all the syntax used here and consider the use of set_rw() method. That’s basically all is needed to make an attribute writable.

If we now get back to the pre- and post-condition specifiers mentioned in the RFC, they also can be implemented with traits using method wrap of Method objects.

I would now skip a few following items in the RFC list we’re walking over now. Some were not implemented, some are just self-evident. Multi-dispatch itself worth an article and I hope somebody would pick it up for this advent calendar.

Let’s just fast-forward directly to the last one:

A new pragma — delegation — that would modify the dispatch mechanism to automatically delegate specific method calls to specified attributes of an object.

This just is another example where traits came to the rescue. There is no delegation in the Raku, but there is a trait named handles (already mentioned in a previous article):

class Book {
    has Str  $.title;
    has Str  $.author;
    has Str  $.language;
    has Cool $.publication;
}
 
class Product {
    has Book $.book handles('title', 'author', 'language', year => 'publication');
}

I chose it since it’s another example of a very elegant solution where no core intervention is needed to implemented some advanced functionality. In two words, the only thing handles does – it installs new methods on the class to which its attribute belongs. Of course, it takes into account some edge cases, tries to optimize things where possible. But, otherwise, there is no magic in it. There is no thing which you wouldn’t be able to do yourself!

This is what I’d like to conclude this article with. Years ago the word magic was kind of trendy among Perl developers. “Here we do some magic” – and then something really unexpected was happening. It was a lot of fun!

However, recently I found an interesting definition of what magic is: a kind of action a person performs to achieve a result which doesn’t logically follow from the action itself.

Sorry for perhaps clumsy translation, but I hope it reflects the idea behind the definition. And makes the good point of the magic while being fun not being good for the production.

In Raku the magic is eliminated. Instead, Raku brought in such a level of uniformity among different levels of code that often the first impression of: wow, this is magical! – is soon replaced with: wow, it’s so logical!

But you know what? When you put everything together and look at the language as a whole, it creates even bigger magic which can easily enchant you once and forever.

 

One thought on “RFC 137: Perl OO should not be fundamentally changed.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.