So far, we have only seen procedural code, but Inform 6 is also somewhat
object-oriented7 Sometimes the Z-machine is described as one of the first
widely-installed object-oriented systems, but there is very little support for
object-orientation in the Z-machine itself. Also zil does not support what we
would today recognise as object-oriented code. It has things called objects, but
they are closer to C structs., with the idea being that messages being passed
between objects is a useful way to simulate interactions in the world. It still
won’t allocate objects dynamically, so for the most part it is used with
singleton objects.8 It is possible to create objects during run-time, but
then they come from a statically allocated fixed-size pool.
Inform 6 supports dual object hierarchies: it encodes is-a relationships through inheritance, and has-a relationships through an object tree indicating containment. We can use the first half of the second day’s puzzle to illustrate both.
To model the second day’s problem, we begin by defining an attribute indicating that a report is safe. An attribute is a boolean flag (in fact, they are called flags in zil) that all objects start out not having, but it can be set on any of them.
Then we create a class for the generic report approver.
Class Report_Approver with ! Store the previous value for range calculations. _prev nothing, ! Method that decides whether to accept a new value. _accept, ! Default reject method that accepts the first value, ! rejects any changes that are too large, and otherwise ! defers to the accept method. _reject [next; if (self._prev == nothing) return false; if (abs(next - self._prev) > 3) return true; return ~~self._accept(next); ], ! When appending a number, if it is rejected, remove ! the valid attribute from this approver. append [next; if (self._reject(next)) give self ~valid; self._prev = next; ], ! To reset an approver, remove the previous value ! and default back to a valid report again. reset [; self._prev = nothing; give self valid; ], has valid;
Here we can see some new features. Properties are like attributes except instead
of booleans, they store values. Importantly, they can store anonymous
subroutines, which are declared like normal subroutines except without a name,
and inside them we have access to the implicit variable self. The keyword
give sets and unsets flags on objects (sorry, I mean “assigns attributes to”
objects, and “removes attributes from” objects).
As before, properties/methods that are not meant to be public are conventionally named with a leading underscore.
Next we define an aggregate approver that judges the validity of a report by
consulting multiple sub-approvers. It will accept a report as long as any of the
sub-approvers accept it. We inherit from the Report_Approver class to do it,
and we override both public methods append and reset.
Report_Approver multi_approver
with
append [next _sub _anyvalid;
! Append to all sub-approvers.
objectloop (_sub in self) {
_sub.append(next);
! As long as any of them are valid...
if (_sub has valid)
_anyvalid = true;
}
! ...then the aggregate is also valid.
if (~~_anyvalid) give self ~valid;
],
reset [_sub;
! Reset all sub-approvers
objectloop (_sub in self) _sub.reset();
! Then perform the same reset as
! the parent class.
self.Report_Approver::reset();
];
The reset method on this object calls the reset method of its superclass.
There are a few ways this can be done9 In some instances we can define
properties as additive and the full inheritance chain is consulted
automatically. but this seemed easiest here.
We also see the objectloop Inform 6 keyword, which starts a special kind of
loop that iterates through the direct descendants of an object in the object
tree.10 We could iterate through the children using object relationship
methods like parent, child, and sibling, but the objectloop is more
convenient and easier to read. As a reminder, the object tree is not the same
thing as the inheritance tree; the object tree is about which objects contain
each other (has-a, rather than is-a).
So far, we have not seen which objects are contained by the multi_approver,
but that happens next!
Report_Approver -> decremental_reports
with
_accept [next;
return next - self._prev < 0;
];
Report_Approver -> incremental_reports
with
_accept [next;
return next - self._prev > 0;
];
This is another way the right arrow is used in Inform 6. When we define objects
with a right arrow, they are automatically inserted as children into the object
defined just before. This means both decremental_reports and
incremental_reports become children of multi_approver.11 There are also
functions to move objects around in the object tree, if they need to move during
runtime, for example.
Finally, we use this by reading in numbers and pushing them into the aggregate
approver, counting the number of approved plans in the local variable _i.
while (_n < 1000) { ! Try to read another line of input. ! Stop if there is no more input. _next = read_line(rbuf); if (rbuf->_next == 0) break; while (rbuf->_next > 0) { ! Extract a number from the input. _next = long_read(rbuf, _next, ax); _next = skip_nodig(rbuf, _next); ! Truncate long integer into short integer ! and send it to the aggregate approver. multi_approver.append(long_trunc(ax)); } ! If the reports are still safe now, ! increment count and then reset. if (multi_approver has valid) _i++; multi_approver.reset(); }
The puzzle input for this day fits comfortably in the Z-machine short integers, but since we already had a method for parsing numbers that happens to produce a long integer, we might as well use it and then truncate it to a short integer.
The next half of that day’s puzzle sounds like it would need expensive backtracking unless done cleverly, and I’m all out of clever for this article, so I’ll stop here. At this point, I feel fairly done with Inform 6 for Advent of Code problems. I’ve toyed with the system and gotten a much better understanding of it. I’m reminded of why I don’t do more low-level programming: it’s a fun challenge, to be sure, but when I write code I do it mainly for the result, not for the challenge. If I want a mental challenge, I’d much rather play the game of go or something.