Z机的Advent of Code
Advent of Code on the Z-Machine

原始链接: https://entropicthoughts.com/advent-of-code-on-z-machine

Inform 6 虽然主要为过程式编程,但也融合了面向对象的元素。它运行的 Z-machine 本身对面向对象支持有限,Inform 6 的“对象”更类似于 C 结构体——利用消息传递来模拟交互。它主要依赖于单例对象,但也可以从固定池中创建运行时对象。 Inform 6 支持双重对象层次结构:继承(“是”关系)和对象树,代表包含(“有”关系)。属性(标志)和属性(值存储,包括通过 `self` 访问的子程序)定义对象的特征。方法可以被重写,例如通过创建一个 `Report_Approver` 聚合对象,根据多个子审批者来验证报告。 `objectloop` 关键字便于在对象树中遍历对象的直接后代。对象可以通过 `->` 运算符链接,自动建立父子关系。示例代码解析输入数字并将它们附加到聚合审批者,统计已批准的计划。这展示了 Inform 6 建模复杂交互和数据结构的能力,尽管作者更倾向于专注于结果而非底层挑战的高级语言。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 在 Z 机器上进行的代码冒险 (entropicthoughts.com) 7 分,by todsacerdoti 1 小时前 | 隐藏 | 过去 | 收藏 | 1 条评论 meindnoch 13 分钟前 [–] 哦。从标题上看,我以为是桑迪亚实验室的 Z 机器:https://en.wikipedia.org/wiki/Z_Pulsed_Power_Facility 回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

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.

联系我们 contact @ memedata.com