After introducing KBOS I should write about the most fundamental concept in this Perl syntax extension. In fact it's so basic, you could use it even without objects.
Of course this is not a full fledged type system. Use Raku to get that. Variables with KBOS will stay your perly whatever data container. But like in Moose or Zydeco, you want to verify data - if its consistent with your expectation. And you don't want to write the checking code lines over and over, plus they pollute method logic anyway.
One of the advantages to have objects in the first place is to be sure, that the attributes obey requirements and you do not have to check them at every function all.
So KBOS types are just glorified perl expressions (one liners) with a short name associated. The other important ingredient are error messages. I want the to be very helpful and never [LTA] (Raku has a high standard for good error messages). That is why I break the type check down into a series of expressions and to each belongs an error message that tell me exactly what was expected of the data during this specific check. So when I declare my type to be catalan and assign to it, I will not get just "missing type catalan" but, "this is not a number", or "this number is not integer" or "this integer is not catalan because ...". So the anonymous subs (partial checks) have a fixed order in which they run and with each run I can make assumption because the value passed the tests before, which makes the code of the next checks easier.
In case you did not already guessed it, types do inherit from each other. So normally I would define the type catalan by inheriting from int+ (positiv integer) and add just one checker on top with its error message and maybe a different default value than the 0 int's have, because 0 is not catalan.
The package Kephra::Base::Data::Type::get_callback('catalan') gives you the ready made and optimized callback ($coderef) where you can throw in your values and either you get an error message as a return value, or when all went well, an empty string. If you declare an attribute to be catalan, you will not even deal with that callback, because that will be handled by the setter implicitly.
My standard example for an relative type (misleadingly called attribute type last time) is an array index. It also has to be a positive integer, but also not larger than the size of a given array. So the value is relative to another value, hence the name.
Beside the mentioned package Kephra::Base::Data::Type there is also Kephra::Base::Data::Type::Relative which has has one major difference to the former: the checking expressions take more than one argument. This distinction is crucial and it enables classes of new use cases. This distinction is also due my wish to create a concise and readable code base. I think it would be inconsistent, if KBOS enables me to create very understandable code but KBOS itself is an opaque wall of characters. Therefore I try keep all packages small, single purpose and easy to understand. And frankly even some amounts of if's in the code (necessary to handle normal and relative types at once) are a code smell to me.
And - relative Types are used in different places. For attributes normal types suffice. But in method signatures the relative ones get handy. For instance in KBOS I can declare inside the signature that an argument has be an index of a certain attribute and the rest will be handled implicitly. If you don't call the method with a valid index, you get the appropriate error message. That keeps the method code lean like explained by toby in his great post.
Allright, but why does KBOS has also Kephra::Base::Class::Attribute::Type? Glad you asked and the answer is simple. Inside each class you can declare your own types which should not collide with other peoples types. I plan KBOS to be used inside of Kephra plugins. Its not rational to expect from a plugin author to know all the internals of the program (that is the purpose of a plugin in the first place). This encapsulation is handled by Kephra::Base::Class::Attribute::Type - objects (one object for each class), whereas the other types are global (usable everywhere, also by attribute types).
But even the global ones have some protections - you can not overwrite their definitions. And you can only delete types you have created inside your current package and never default types.
Next time I write about how types are used in KBOS signatures.