Subject Verb Object notation; declarative Perl without the framework
If you’ve read Curtis “Ovid” Poe’s articles on the declarative framework for Tau Station Link and Link, you are undoubtedly aware of many of the benefits this style of programming can bring. It decouples the “what” from the “how”, encourages discrete functions and prevents the OO trap of “god objects”. The result is software that is easy to test, robust and very flexible. Inserting steps, reording steps etc… are done much easier and more clearly than trying to figure this out in 300 lines of imperative code with four to six level deep if-else chains with for loops mixed in for good fun. However, the framework is tightly coupled to the game in spots and has a few other issues that make it not ready for general use.
One of the features of this framework is the ability to declare the “what” using subject-verb-object pattern. So I recently set off on a search to see if this already exists in the Perl ecosystem. I found what I was looking for right in the official Perl docs. Method Call Variations
my $obj = Tree->new();
$obj->Dog::bark();
The docs come with this warning:
“This calls the bark method from class Dog on an object of class Tree, even if the two classes are completely unrelated. Use this with great care.”
Which in my opinion should be removed from the docs and this paradigm promoted as a language feature. Also, Dog does not have to be a class in the OO sense but just a package, so package Dog can contain non OO functions.
The framework also has some keywords, “ASSERT, FAILURE, ALWAYS”. Couple the above with Try Catch to get a similar effect. Giving you code that looks like this:
try {
$character->Location::is_in($location);
$character->Wallet::contains($credits);
$character->Wallet::pay($amount)
} catch {
…handle error, rollback credit deductions etc…
} finally {
print $character->credits;
};
Steps can contain other steps, so the Wallet::pay could be a combination of:
$character->Wallet::debit($credits);
$other->Wallet::credit($credits);
Add in your own messaging service to the function classes and a stash or cache system to carry intermediate values through steps; and you are well on your way. Programming like this does require greater discipline. Keeping functions discrete and objects small. Your objects should be little more than smart structs / dataclasses. All computation is done in functions, something a fellow developer called “object shell, functional core”. One of the metrics for if a function was discrete enough turned out to be that most functions did not contain more than about five lines of unique expressions. Once you took out get the args and the “outcome” return. This makes for very easy testing, reording of steps and easily assembled aggregate functions.
And that’s it, declarative programming in perl with SVO (Subject-Verb-Object) pattern. Here is a quickly hacked together - incomplete but working - example:
as equivalent toI've never found this intuitive, and whenever I've written fully qualified method names, I've always used them as if that wasn't the case, i.e. using
Generally, I try to hide all such technical details. A method can decide to do it's work any way it likes, including calling different packages. However, when you subvert all of that to make resolution specific, you now have to change every use if you decide to use something else.
I'd much rather see this:
Not only that, but each of those functions now has to figure out which sort of $noun called it. If you say that it will only take one sort of noun, there's not a reason to add the complexity at the application level because the usual method name stands in for all of that (once again hiding the the particular details).
But, adding roles here is often a big mess. A Character may contain a wallet, but a Character is not a Wallet and shouldn't do wallet things. Making much more complex objects with roles often leads to collisions in method names . You don't want to simply throw a bunch of methods into a class. Some people call this the "God Object" anti-pattern.
I think both or these are consistent with what I expect.
Wallet is not a great example other than Wallet holds credits and anything doing the Wallet role can use this interface. The benefit of this is much more clear with Location is_in. $character, $ship, $item, etc.. all need to use this, so $ship->Location::is_in($area) and $item->Location::is_in($area) all work without each object needing the necessary method. This all is clearer is you read here. You could use roles here in the traditional OO way but, this pattern eschews objects from having any methods. As I said, this requires more discipline, naming conventions is one of those area. This pattern as "Ovid" noted in one of his articles, is closely related to ECS. In ECS, objects are merely IDs. Those IDs only carry state. State is composed onto the object with "components" that are from my view, traits. Systems are messaged that an ID needs servicing, the system then acts on the state through the component. What I am showing only slightly changes this so that entities are handed to the system that acts on them. That system (Transportation, Inventory, etc) acts on the entities that have the correct interface. Objects are super light weight as they have no methods, instances no longer have a bunch of unneeded methods for each instance when in general an instance only needs a small subset of methods at any time. This pattern works with objects and in non OO languages, structs. In my biased experience, this makes for large applications that are much more flexible and at the same time, less fragile. As for implementation details, this is just calling a function by the fully qualified name instead of a method, the "how" is still encapsulated.
If
Location
isn’t inheriting from anything then these are just functions masquerading as methods, obscured by the use of a special case of the arrow operator. Not sure what the big revelation is.If ECS is the actual revelation here, then that is a good thing to understand, but it’s also not a tool that can replace every application of OOP. It’s good for what it’s good for, and trying to use OOP for things where you really need ECS will be painful, but that doesn’t mean ECS is applicable everywhere OOP is. In fact it’s suited to a much smaller set of problems than OOP.
(And I sure wouldn’t rely on this syntactic variant of the arrow for an ECS design, much less view them as somehow synonymous or even related.)
Yes, Location is just a function masquarading as a method. I think I said as much in the reply above. The point of this article was to point out how you could get close to the style of programming that powers Tau Station without the framework. An ECS inspired, declarative style. If this fits the problem domain great, if not, great also. Hammer...nails, etc... and not all hammers are great for all nails. I agree with your comment about ECS and OOP. The pattern I am showing here does have it's benefits and might just be what someone is looking for for the problem they are facing. Revelation? Nah. However, I don't suspect this is a well known language feature or how it could be used for benefit. Somethings just aren't going to be everyones cup of tea. I'm ok with that.
But you haven’t shown any benefit of the language feature. The code examples you showed could just as well be written like this, and would mean and do the exact same thing:
So I guess the insight here boils down to “you don’t have to have every callable available as methods on your objects, especially callables that apply to lots of different kinds of object – passing objects to functions is just fine”. The syntax doesn’t come into this at all, the most it does is to obscure it.
Correct. But if you read the title, it says, "Subject Verb Object notation" not Verb Subject Object. That is where the syntax comes in.