This is a really fascinating essay. I myself am rather fond of rule-based systems. I enjoy Prolog quite a bit but don’t see a straightforward adaptation of this idea. From that perspective though, the author’s main problem is sort of that he wants a single rule to “win” in the end: we don’t want to get both the “It’s too dark to see anything” whatever the human output is (say it’s “You see the grandiose landscape of the human face”) in the same moment. One of them needs to win, and thus the whole problem emerges. But this is not necessarily true of other rule-based systems. You could view Prolog’s execution model as embodying the idea that you’re going to generate all the solutions and allow other elements of the query to discard invalid ones, ultimately rather expecting the human operator to make the final decision about whether the query is satisfied.
CLIPS is a rule-based system and solves the problem with explicit salience. This is something Andrew seems to dislike, and I get why: I might be able to handle salience in one “chapter” but across chapters it will be irritating and when we factor in libraries it’s going to get really troublesome unless there’s some way for me to modify the salience of library rules, which is quickly going to get gross.
Both CLIPS and CHR (which has implementations in Prolog and some other languages) work by sort of creating a large pool of facts and having rules that automatically trigger based on what’s in that pool. This is a cool way of solving logic puzzles and doing other constraint-ish things. If you were going to use one of them to build your IF backend, you would still want that REPL flavor of read action, perform action, report result. The perform action phase here seems like the one you want to throw at your rules engine, but as he points out, it’s messier than you’d like. You want rules to rewrite the actions that got read in (so that things like “go” and “walk” can reduce to the same thing) but you also want to be able to inhibit or redirect actions, and that means putting it through the rules engine. Similarly you want the print stage to be overridable by rules.
It feels like whatever nice logical structure may exist here, it’s being poisoned a bit by mutation and the interleaving of effects (printing, mainly). In a graphical program, you could easily wire up a result to an input; the human could then just see the result. If you walk into a dark room, you already can’t see, there’s no need to tell you that you can’t see. If there’s a human in the room, maybe they’re getting rendered, but it doesn’t matter because there’s no light source bouncing rays off them. It seems like maybe IF is stuck with some trouble here because the system needs to know what the most salient property of what just happened is and report that to you. In fact IF often coaches you towards the right thing to do, and all that coaching is state-derived, as you can see in his examples. In a 3D rendered world, you make the action have all the physical effects it’s going to have, and the human just absorbs them, and hopefully notices the most salient aspects on their own.
I feel like a lot of the problems regarding text output that you describe also happen, to some extent, in MUDs. I think that you can be more stringent in your programs and deduce the proper result without succumbing to the nondeterminism of rule matching.
Here’s a sample that a colleague and I whipped up years ago, in a rule oriented language that’s roughly similar to CHR.
It’s a new language with fairly trivial semantics. The idea was similar to tuple spaces: your entire game/application state was stored as a set (proper set, with uniqueness) of variable-length tuples that conveyed some piece of information. Programs were phrased as rules that consumed tuples and wrote new ones to the set.
Each rule’s left hand side and right hand side contains lists of tuple patterns to match on. Each pattern is terminated either with a ? or a ;. If it’s terminated with a ?, we just check that the tuple exists in the store. If it’s terminated with a ;, we check for the tuple’s existence, and if it does, we remove it from the store.
Having destructive and non-destructive read operations means you can process actions as well as query for long-term knowledge. It also functions loosely as a concurrency gate: a rule can “acquire a lock” or “consume a resource” to do what it needs to.
Rules are also atomic. It’s all-or-nothing. If a tuple on the left hand side doesn’t exist, the rule doesn’t fire. In this particular example, we order rule firing by precedence, because as you probably noticed, there are overlapping rules that gradually get more and more generic as you scroll down through the source code.
Variable binding is also pretty trivial: first use of a variable binds it, second use of a variable is an equality check. So you can use parts of previous tuples in patterns for new tuples.
Yes, but the implementation exists on an archived disk image. I could probably write it again.
The actual implementation isn’t hard. The implementation in question stored tuples in a trie to facilitate matching. A pattern is just seen as a path through the trie, with variables acting as choice points you can backtrack through.
You could probably come up with this in Prolog or uKanren. If you want more details, I’m on Libera.chat as imode. :)
I haven’t seen it before, but I really, really like it. It seems like a much better version of whatever I had going on!
These days I’m trying to focus more on general-purpose rule based programming rather than interactive fiction, but IF is a perfect testing ground due to its complicated edge cases and features that can reach across your codebase.
I like the idea of “additive programming”, where your program facets/features can be added and subtracted from without adhering to a larger structure. Rules are one way of doing that, but I’m trying to figure out others.
This is a really fascinating essay. I myself am rather fond of rule-based systems. I enjoy Prolog quite a bit but don’t see a straightforward adaptation of this idea. From that perspective though, the author’s main problem is sort of that he wants a single rule to “win” in the end: we don’t want to get both the “It’s too dark to see anything” whatever the human output is (say it’s “You see the grandiose landscape of the human face”) in the same moment. One of them needs to win, and thus the whole problem emerges. But this is not necessarily true of other rule-based systems. You could view Prolog’s execution model as embodying the idea that you’re going to generate all the solutions and allow other elements of the query to discard invalid ones, ultimately rather expecting the human operator to make the final decision about whether the query is satisfied.
CLIPS is a rule-based system and solves the problem with explicit salience. This is something Andrew seems to dislike, and I get why: I might be able to handle salience in one “chapter” but across chapters it will be irritating and when we factor in libraries it’s going to get really troublesome unless there’s some way for me to modify the salience of library rules, which is quickly going to get gross.
Both CLIPS and CHR (which has implementations in Prolog and some other languages) work by sort of creating a large pool of facts and having rules that automatically trigger based on what’s in that pool. This is a cool way of solving logic puzzles and doing other constraint-ish things. If you were going to use one of them to build your IF backend, you would still want that REPL flavor of read action, perform action, report result. The perform action phase here seems like the one you want to throw at your rules engine, but as he points out, it’s messier than you’d like. You want rules to rewrite the actions that got read in (so that things like “go” and “walk” can reduce to the same thing) but you also want to be able to inhibit or redirect actions, and that means putting it through the rules engine. Similarly you want the print stage to be overridable by rules.
It feels like whatever nice logical structure may exist here, it’s being poisoned a bit by mutation and the interleaving of effects (printing, mainly). In a graphical program, you could easily wire up a result to an input; the human could then just see the result. If you walk into a dark room, you already can’t see, there’s no need to tell you that you can’t see. If there’s a human in the room, maybe they’re getting rendered, but it doesn’t matter because there’s no light source bouncing rays off them. It seems like maybe IF is stuck with some trouble here because the system needs to know what the most salient property of what just happened is and report that to you. In fact IF often coaches you towards the right thing to do, and all that coaching is state-derived, as you can see in his examples. In a 3D rendered world, you make the action have all the physical effects it’s going to have, and the human just absorbs them, and hopefully notices the most salient aspects on their own.
Fascinating problem.
I feel like a lot of the problems regarding text output that you describe also happen, to some extent, in MUDs. I think that you can be more stringent in your programs and deduce the proper result without succumbing to the nondeterminism of rule matching.
Here’s a sample that a colleague and I whipped up years ago, in a rule oriented language that’s roughly similar to CHR.
http://okturing.com/src/12884/body
This is quite cool, what’s the language here? Do you have more details about it?
I’ve been interested in MUDs for a while but found the programming on all the varieties rather gross looking but this looks great!
It’s a new language with fairly trivial semantics. The idea was similar to tuple spaces: your entire game/application state was stored as a set (proper set, with uniqueness) of variable-length tuples that conveyed some piece of information. Programs were phrased as rules that consumed tuples and wrote new ones to the set.
Each rule’s left hand side and right hand side contains lists of tuple patterns to match on. Each pattern is terminated either with a
?
or a;
. If it’s terminated with a?
, we just check that the tuple exists in the store. If it’s terminated with a;
, we check for the tuple’s existence, and if it does, we remove it from the store.Having destructive and non-destructive read operations means you can process actions as well as query for long-term knowledge. It also functions loosely as a concurrency gate: a rule can “acquire a lock” or “consume a resource” to do what it needs to.
Rules are also atomic. It’s all-or-nothing. If a tuple on the left hand side doesn’t exist, the rule doesn’t fire. In this particular example, we order rule firing by precedence, because as you probably noticed, there are overlapping rules that gradually get more and more generic as you scroll down through the source code.
Variable binding is also pretty trivial: first use of a variable binds it, second use of a variable is an equality check. So you can use parts of previous tuples in patterns for new tuples.
Did you implement this?
Yes, but the implementation exists on an archived disk image. I could probably write it again.
The actual implementation isn’t hard. The implementation in question stored tuples in a trie to facilitate matching. A pattern is just seen as a path through the trie, with variables acting as choice points you can backtrack through.
You could probably come up with this in Prolog or uKanren. If you want more details, I’m on Libera.chat as
imode
. :)Have you come across this Dialog platform for interactive fiction? I just ran into it today and wondered if you had seen it.
I haven’t seen it before, but I really, really like it. It seems like a much better version of whatever I had going on!
These days I’m trying to focus more on general-purpose rule based programming rather than interactive fiction, but IF is a perfect testing ground due to its complicated edge cases and features that can reach across your codebase.
I like the idea of “additive programming”, where your program facets/features can be added and subtracted from without adhering to a larger structure. Rules are one way of doing that, but I’m trying to figure out others.
If you have a minute, do you have any resources about general rule-based programming you could share?