Introducing KBOS
Starting even before Moose, we (in the Perl 5 world) have a plethora of Modules extending the syntax of the language with Perl 6 and more in mind. The following article sums up not only my 2 and a half cents on the subject but also an attempt to implement it. It should be of interest to anybody thinking about programming in general.
As many here know, Kephra is the project closest to my heart and during the latest iteration, I decided to extend the language itself to get a more expressive, less repetitive code base. I want a fast, extendable type system with helpful error messages, real private attributes, real private methods, signatures with typed, positional, named and optional arguments, relaxed professional error handling, I want to know all instances of a class, reuse by delegation and incorporate any foreign objects. Last not least should the system support me in marshalling all attributes, so I can fully restore a program state after restart or switch into a remote session / other window.
The Kephra Base Object System (KBOS - read: ok boss) is designed to deliver on all that and I just want to discuss here my decisions. Some seem to be strange, like no inheritance (a feature), relative types (not even Raku has them) or 4 different method scopes. But hej its my pile of garbage, stay away. I want this to become the optimal object system for Kephra's needs. It is not clear to me if I will release it or parts as a separate distribution in future.
For today maybe I present the rationale behind the 4 tier scope system, because that is what I did yesterday at the HalleLeipzig.pm meeting. Future posts will be dealing with other aspects.
Privacy Please
Given a class (starting the the class keyword) named Class and a bunch of methods and attributes (with their specific keywords) in a loaded file that got compiled. To create an object, just call $obj = Class->new( $arg, ....);. So far, so expectable. say ref $obj, outputs of course Class, but inside a method my $self = shift; say ref $self; will give you Class::PRIVATE - a very different name space, where all the public and private methods are present. These private methods are not present in Class. How did I achieve this ? - by Keyword::Simple. If the KBOS-enhanced Perl-sources contain
private method (...sig def..) { .... code ...}
the keyword method triggers during BEGIN (compile)-time a Keyword::Simple - callback. That rewrites the source code (slightly tamed source filter) into
Kephra::Base::Class::Builder::create_method('private', "...sig def..", sub { ... code });
This altered source will be compiled by perl and run. Because only Perl can parse Perl I let perl deal with the code inside the sub and the resulting coderef will be mounted into the right name space by calling ...Builder::create_method. Of course there is happening much more, but that is the important part for todays topic.
Attribute ACCESS
Attributes are not accessible at all, because what is the point of having attribute types if you can not enforce them and let every schmock put any value into them. To avoid that, I auto generate getters and setters that check for type constrains. But even these Methods live in a scope so obscure, you have to know the internals to access them (unless you define them as private or public). The reason for this hassle is two-fold.
One - you want to get a well regulated access to each attribute separately. Some attributes can be seen by everybody, but only written inside the object. But some data you want to keep in the background just noting a state you can not influence directly by a command, not even in private methods. That is why inside an accessor methods (getters and setters) the $self has a different scope, than in private or public ones. In that access scope (Class::ACCESS) you can see all public and private methods, plus the getter and setter that live only in access scope.
The second reason for that model has to do with the lack of inheritance. Instead of inherit methods from an different class you use an object of that class as an attribute. So you get a code structure, where your private methods are the working logic of your class, that should be as straightforward and dwimmy as possible. Your public methods are the translations and API to the outward world and accessors are the same to the inner world (attribute data and attribute objects). Naturally you want to encapsulate that inner layer from the outer too. Then changing an attribute type affects only code of the inner layer.
To not forget the main thing: each accessor, which is not auto generated, is assigned to one attribute and gets the full access to this one attribute (but can not override the type checks ofc, which are done my the auto generated, inner setter).
BUILD scope
The fourth scope is again much simple to explain. Whey your inside the constructor (new) or deconstructor (demolish) you want to have full access. I mean at some point the value of an attribute has to be set. So in BUILD scope you see all the previously mentioned get/ set methods of all attributes. This way you can have attributes that can only be set at start and only be even seen when the object is demolished.
Next time I will talk more about the type system - its the real base of it all.
P.S.: The names spaces e.g. for private scope are actually not ClassName::PRIVATE, but ClassName::-::PRIVATE and so forth to prevent some clever hacker to overwrite somthing by creating a public method named PRIVATE.
I'm interested in how well this technique for method privacy works out.
For me, the golden test would be something like (pseudo code):
If Derived has a public method called foo is where things get more tricky. Like, should Base::bar see its private method Base::foo, or should it see Derived::foo?
If Base will see Derived::foo, I think Derived should at least get a compile-time warning about overriding a private method with a public method.
Generally speaking, I find using lexicals for private methods to be a good solution.
The call syntax Base::bar uses to call Base::foo is kinda ugly, but it works, and it's obvious what's happening.
Sorry, first example should have been:
i see your issue, but luckily I don't have even think about and hut my precious brain, because KBOS has no inheritance (for reasons like you present here and as written in the post). But to fully adress you question, I think you should be able to call inside Derived $self->parent->foo and rest as normal.
Hmm, but what if Base were written using KBOS, and Derived was just a plain old Perl package with
use parent "Base";
? Even if you're not implementing facilities for inheritance, it's hard to ignore that inheritance does exist in Perl.well, KBOS classes are not ment to be inherited from. If you define a class Bumblebee, there will be later no package Bumblebee, let alone subs in it, so parent would run empty. All information which methods and attributes the class has is stored in Base::Class::Definition. But maybe i should add some support for such usage, if i should release the as distro.
Just as addon, the B in KBOS is meant literally. What I show here is merely the base level. There is also a logging, event and UI comand system involved. So I naturally never thought about so far, that other classes want to be part of this integrated system, but if KBOS is a standalone thing i whould.