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:

Screen Shot 2021-03-23 at 8.29.43 AM.png

Screen Shot 2021-03-23 at 8.30.12 AM.png

Screen Shot 2021-03-23 at 8.30.41 AM.png

Screen Shot 2021-03-23 at 8.31.22 AM.png

8 Comments

When you call a fully qualified method name like File::save, the method resolution search for the save method starts in the File class, skipping any save method the File::MP3 class may have defined. It still searches the File class's parents if necessary.
I'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

$foo->Bar::narf(@p)
as equivalent to
Bar::narf( $foo, @p )

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:

$character->location->is_in()

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.

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

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:

try {
  Location::is_in( $character, $location );
  Wallet::contains( $character, $credits );
  Wallet::pay( $character, $amount )
} catch {
  …handle error, rollback credit deductions etc…
} finally {
  print $character->credits;
};

# ...
Wallet::debit( $character, $credits ); Wallet::credit( $other, $credits );

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.

Leave a comment

About Jesse Shy

user-pic Perl pays for my travel addiction.