Dialog

I’ve got one issue, arguably more of a feature request for the manual. Would it be possible to have a generated, clickable index for the concepts and predicates discussed in the text?

For instance, while reading the manual, I several times forgot what the “Singleton” predicate meant (possibly due to too much exposure to the Book of Four), meaning I had to open each manual page and search for the term. It strikes me that implementing this sort of tag-based index structure would have utility; one could even throw in a reference to or quote from its Standard Library definition.

(The last one would have been incredibly useful when hacking I6 templates for Inform 7; arguably not needed in Dialog, but as long as you’re wishing for a pony…)

1 Like

Release notes for Release 0f/07, library 0.24:

  • Documentation: Added a predicate index with links to the relevant sections of the manual.

  • Documentation: Clarified the role of em and ch, and the whitespace requirements of CSS.

  • Library: Modified the code to comply with the previous bullet point.

  • Library bugfix: With fungibility enabled, under certain circumstances, object appearances didn’t get printed.

  • Z-machine backend: Em-dash, en-dash, and three kinds of fancy quotes (“ ” „) are now safe to use in stories. They are rendered correctly on interpreters that support them, and approximated using ASCII characters elsewhere.

3 Likes

I’m having trouble with disambiguation and scope involving third party actors.

Consider this code:

#room
(name *)    test chamber
(room *)
(look *)
    Nothing to see.

(current player #player)

(#player is #in #room)

#person
(name *)    man
(animate *)
(* is #in #room)

#red
(name *)    red block
(* is #heldby #player)

#yellow
(name *)    yellow block
(* is #heldby #player)

#blue
(name *)    blue block
(* is #heldby #person)

(perform [tell #person to drop $Something])
    "Sorry, but no! I won't drop (the $Something)"

(unlikely [tell #person to drop $Something])
    ~($Something is #heldby #person)

Test this with man, drop block. Remove the yellow block, and test again.

When parsing the action which forms the “command”, the parser is looking at things from the player’s point of view. If you remove the yellow block, then it will always choose the red block over the blue block (despite the unlikely rule), and if the player holds two blocks, it will always disambiguate between those two (despite the unlikely rule), and will not even offer the blue block as an option.

I suspect (but I can’t follow the code completely) that this is because the parse of the “command” invokes the parser for the “action” as if it were a command typed by the player, so that it naturally prefers to drop an object that the player holds over one that someone else holds. But in this case that is not appropriate, and I don’t think my own unlikely rule is even getting a foot in the door.

Thanks for the predicate index. I understand that because the library is still in development, isn’t the time for a library quick index/reference, but please don’t forget…

Best regards from Italy,
dott. Piergiorgio.

I believe this is now fixed in Standard library 0.25.

The problem was indeed that nouns were evaluated based on their relation to the player, rather than the actor. But it happened already during parsing, e.g. when doing (understand $ as object $ preferably held). By the time (unlikely $) was involved, the set of candidate objects had already been reduced to those held by the player.

This is probably a good time to explain why the object sieving is done in two phases. It is useful when dealing with complex actions with both failing and non-failing parts.

Suppose the player comes across a red jewel, a green jewel, a golden coin, and a huge golden statue. They’re already holding a blue jewel. Now an attempt is made to “take jewels and statue”. Clearly, “jewels” should be understood as the red and green jewels, but not the blue one which is already held. But taking the statue should be considered unlikely, because it’s too heavy to lift. By declaring (unlikely [take #goldenStatue]), we tell the library that “take golden” is to be understood as taking the coin.

If we use the same mechanism to determine what “jewels” refers to as we use to determine what “statue” refers to, we will find that the player is attempting to take the red jewel (likely), the green jewel (likely), the blue jewel (unlikely), and the golden statue (unlikely). But we can’t just disregard the unlikely options, because then the player’s explicit request to take the statue would be silently dropped. Instead, we want to attempt that action, and have it fail.

That is why, in this case, (understand $ as object $ preferably takable) silently removes the blue jewel from the complex action, before (unlikely $) is consulted in order to do further disambiguation.

1 Like

Dialog 0g/01, library 0.26, is out. It features a new compiler backend for the Å-machine, which allows you to put Dialog stories on the web with extensive CSS support. The Å-machine story format is compact, and potentially quite fast on vintage hardware, although that remains to be proved.

Z-machine support remains at the heart of Dialog, and will not go away.

Language-wise, the only new thing in 0g is hyperlinks, which can be used on the Å-machine to simplify text-entry on mobile devices.

Explore online play with a new release of Tethered, my IFComp 2018 entry.

Release 2 of Cloak of Darkness (Dialog source
code
) illustrates how to enhance touchscreen play with clickable links.

Note that there are some known issues with certain browsers, and so far screen readers don’t work. More details are in the Å-machine thread.

4 Likes

This is just beautiful stuff. Now I really want my next project to be in Dialog.

I found this in the manual, in chapter 9:

Closures could be added to the language to support callbacks. There are a handful of places in the standard library where this would make the code substantially cleaner and easier to extend from the outside.

You added those in the penultimate update, right?

1 Like

Yes, good catch! I’ll fix that, but I promised myself a few days off. =)

I also forgot to include some of the new files in the distribution archive. Release 0g/02 contains the complete source code (and is otherwise identical with 0g/01).

2 Likes

Is there a plan of creating a basic editor/editor plugin once things are more cemented? It’d be amazing to have some nice integration with dgdebug or have rudimentary completion to make writing easier.

And huge props to @lft—This looks awesome. I’ve always been a bit apprehensive of diving into tools like Inform as it seems to be a bit much of a read-only language, even more so than AppleScript. I really like the design choices that have been made and I’m really excited to use it.

1 Like

I just discovered Dialog last week and I’m very, very impressed with it. So many great design choices. It’s no exaggeration to say that it is the IF authoring system I’ve been trying to create ever since I found myself frustrated with Inform 7’s over-reliance on custom syntax and multiple awkwardly-interacting paradigms.‘Predicates for everything’ is just nicer and simpler to hold in the head.

I’m in the process of porting my ‘Planner’ extension to Dialog and it’s an absolute joy to write goal-planning scripts in the way I always felt they were meant to be: as lists generated by predicates. The AI code, like everything else in the game code, just feels simple and pure.

I feel like Dialog also points the way towards something even more interesting than just an IF platform: a way to tame the complexity of Prolog and make it more suitable for everyday programming. I especially love three breakthrough ideas you’ve introduced, which were already kind of there but needed a bit of nudging to bring to the surface:

  • that predicate calls do NOT return multiple results UNLESS explicitly marked with * at the call site. This tames something that’s always bugged me in my attempts to do IF in Prolog: how a mistake in one predicate might lead to multiple executions of another predicate, which is really nasty if side effects are happening. Forcing ‘multi-queries’ to be explicit gives us the normal ‘one call gives one result’ behaviour that we expect from other programming languages, but when we really need multiple values, we can still get it.

  • leaning into side effects as data values. So that bare values (or lists) in a predicate definition, which have no defined meaning to Prolog, just get written to output. And can be captured using (collect words). That’s a really powerful mechanism, I think. It’s always been awkward in Prolog how there’s no good equivalent of functional or dictionary-entry definitions. But using side effects as data values, we can get that. (I’m almost wondering if we couldn’t somehow have a (collect changes) which collects up (now) statements into a data structure rather than immediately applying them, so that we could make predicates strictly side-effect-free if we wanted… but that would require some extensions that might not fit the Z-machine)

  • Predicates as just free-form lists. That is amazing and I don’t think I can overstate how much it solves another long-standing major annoyance with Prolog: the lack of a good way of naming predicates. A predicate with more than about two parameters expresses a VERY complex relationship, and since the language has no notion of ‘type’ (something I admire) it needs careful annotation to show what kind of values are expected in what parameter slot. And normal Prolog only gives us one word at the left to describe all of this. But if we can just use any arbitrary English phrase, suddenly our code becomes almost self-documenting.

And some other things:

  • No trying to reverse enginer black-box algorithms for how rules get ordered. Everything is just source code order, earlier lines override later lines. And we can assert negated predicates to cancel the behaviour of later ones (and don’t even have to do this if they aren’t multi-queried). So simple. Together with inspection of the library source, this immediately gives us defaults, inheritance, aspect-oriented programming. I don’t know why all the logic community got so bound up for decades in trying to nail down ‘the semantics of general logic programs’ but for actual programming, asserting negations are exactly what we need. (And I think are also the huge breakthrough that McCarthy made when he created the ‘cond’ expression, which led to the if-then statement, which also ran against the grain of the logic-driven functional theory of the time. The idea that you could tame unbounded recursion just by… ordering the conditions in your statements and choosing not to evaluate the second one! That was pretty scandalous at the time, I think, and seems to have remained so for much of the logic programming community who kept trying to ditch the cut and make everything run in parallel. But it’s important that we keep the idea of source code (or data) ordering. It’s how we model change and exceptions.)

  • No double quoted strings. It’s just a little thing but it makes a text-based language immediately so much clearer to understand. Even more so than Inform 7.

  • No commas. That syntactic noise always bugged me about Prolog. It’s now got the clarity of Lisp.

  • Easy to visually distinguish (predicates) from [lists] which is helpful since the first are compile-time structures and mostly static while lists are mostly runtime and dynamic.

  • $ for variables is simple and obvious for modern programmers and avoids that other historical nasty of Prolog, uppercase variable names. And now we can escape them with \ .

  • #object names are great for debugging and let us tell instantly the difference between an object, a dictionary word and predicate descriptions

  • {} for conjunctions also solves another big syntax mistake Prolog made, which was to overload () to mean both predicate and conjunction.

  • (if)(then) blocks are awesome, I’ve always missed those in Prolog. Having to repeat a test twice always irked me.

  • the debugger is wonderful. Being able to execute arbitrary predicates and see the call tree, priceless. I’m still deciding if I can trust the automatic source code loading.

  • non-nestable predicates… is the right choice for the Z-machine, I think, though for a larger language I’d like to see Prolog terms come back.

  • the lack of arbitrary dynamic predicates also is the right choice for the Z-machine. Again in a larger language I’d like to see them come back but I think I don’t miss them much in IF, given predicates everywhere, true untyped lists and the object tree. For so many things you don’t want a dynamic data structure, you just want a predicate which is indistinguishable from one.

Anywa, huge congratulations for making this. I think it’s as big of a step forward for Z-machine programming as both Inform 6, 7 and the source code release of ZIL are. I know I’d much rather write in Dialog now than either of those.

7 Likes

Here’s what a Planner rule looks like in Inform 7:

Being-open is a planning-relation.

Planning-testing when the desired relation is being-open (this is the basic testing being open rule):
	if the desired param1 is open, rule succeeds;
	rule fails;

Planning when the desired relation is being-open and the desired param1 is openable (this is the basic opening rule):
	plan 1;
	suggest being-unlocked with the desired param1;
	suggest being-touchable-by with the desired param1 and the planning actor;
	suggest doing-opening with the desired param1;
	rule succeeds;

and here’s what it looks like in Dialog:

(goal [$X open])	
	($X is open)

(plan [$X open] $P)
	(openable $X)
	($P = [[$X unlocked] [$X reachable] [do open $X]])

It’s just… ugh. There’s no comparison, actually. The second version is just lightyears ahead, and yet Prolog is still 1970s technology. It’s just that even in 2019, we’re only just catching up.

And this is why I stopped developing in Inform 7.

And some of the core loop of Planner for Dialog showing how free-form predicate names really help:


agenda $A advances [] accumulating $A for actor $)
(agenda $NewA advances [$Purpose|$Purposes] accumulating $AccA for actor $Actor)
	{ (if) (plan tracing on) (then) planner: purpose $Purpose (line) (endif) }
	(purpose $Purpose has live subpurposes $Subpurposes for actor $Actor)
	(append $Subpurposes $AccA $MergedA)
	(agenda $NewA advances $Purposes accumulating $MergedA for actor $Actor)

At least they help me. Looking at these predicate names I can instantly tell what ‘type’ each parameter slot should be (an Actor, an Agenda, a single Purpose, a list of Purposes… which are actually an Object, a List of List of Lists, a List of Lists, and a List of Lists of Lists). It’s especially helpful to be able to put the ‘type’ name next to the variable in cases of destructuring-binds… which I use as much as I can… because otherwise the variable name doesn’t give you any help.

None of this excellent documentation aid is available in traditional Prolog, where you’ve got one word to describe the entire predicate. It’s ‘just cosmetic’ and yet it just much feels like the jump from FORTRAN single-letter variable names, to the modern idea of variable names as whole words which Lisp gave us and which is now standard.

Ironically, Inform 7 does this ‘sentence as name of routine’ thing yet it… makes everything longer and more cumbersome and harder to read. But in Dialog, I get the best of both worlds. I can use long predicate names where I need to, in the intricate internal bits which no-one else will call, for clarity, and then use very short ones like (plan $ $) for user-facing bits where the names need to be terse and get out of the way.

4 Likes

Really makes you wonder what a native Clojure DSL could look like for writing interactive fiction :slight_smile:

(That would lack the awesome string syntax that Dialog has, though, among other things. The more I think about it, the more I realize how great Dialog is for IF.)

These points are extremely interesting.

Of only peripheral interest, I wrote something on my blog awhile back about functional language clarity tradeoffs, essentially asking if there were any. (Link) That’s a non-IF article, incidentally. It spurred a lot of interesting discussion then and still does today in my career.

Back on point, what’s interesting to consider is that you give us two Planner versions to consider. So when looking at Dialog’s version, we know what you intend because we have the Inform 7 version staring at us.

But would someone be able to as easily figure out the Dialog version if they were doing source diving and didn’t have that comparison? The same could apply to Inform 7, of course. Even with your Inform 7 example, could I rename “param1” to be something more indicative that would make it more readable?

Obviously you can say, “Well, if you know the language, then yes, then someone could very easily figure out what my Dialog code is doing.” That’s a point I was making in my blog post. But there’s a level of expressiveness that is possible even when we don’t know the language or when we don’t know all of the language. (I’m still learning little idioms in Python and Ruby and I’ve been using both languages for years!)

I’ve never seen your Planner prior to now but just reading the Inform 7 source, I got the gist of it right away. I’m not sure I would have understood the Dialog source if I didn’t already know what it was being compared to.

None of this is to dismiss your substantive points at all. In fact, I’m very curious to see more. I like when languages are compared in terms of expressivity and, in general, I am like you in that I often point out to people that all the new stuff we do is often just playing catch-up with the stuff we already did a long time ago.

4 Likes

Dialog release 0g/03 includes several minor bugfixes.

The Z-machine backend now also recognizes ‘font-family: monospace’, e.g. for divs that contain newspaper text.

3 Likes

Your point is well taken. It’s always hard to answer these questions, but having spent quite a bit of time with Dialog now, I think the answer is “pretty quickly”.

The syntax is very regular. As with any other language, and as you point out, fluent reading depends on becoming familiar with some idioms. But because of the simplicity and regularity of the syntax, it’s usually quite easy to learn. There aren’t all that many idioms, so they recur frequently. There’s not too much synctactic sugar, which I generally find is as with sweetness generally, “whereof a little more than a little is by much too much”.

I quite quickly grasped (I think) at least the outlines of the code that natecull posted. There are some things I don’t immediately understand: but I can immediately see that what is happening is that given a list consisting of a goal, plan $ $ produces a list-of-lists consisting in turn of things that also look like goals. The core loop planner is more difficult, but once one is familiar with the idiom of recursive predicates, consuming a list, it’s quite obvious that that is what is probably what we are looking at. In terms of code-diving, I can also see that the key thing to understand in the core loop planner is likely to be the predicate purpose $ has live subpurposes $ for actor $, so I know what I am looking for next. FWIW I can also quite quickly put my finger on some things that I don’t immediately understand (such as why reachable $X is not reachable $X for $Actor), and I can therefore guess that there must be something somewhere that closes that gap.

I can certainly report this: I find surprisingly often that even quite complicated code in Dialog works first time. Syntax errors, as such, are very thin on the ground. That suggests a tidy syntax. And when it doesn’t work, the ability to enter queries in the debugger is hugely helpful in working out why it isn’t doing what you expected. It’s also refreshingly free of niggles over things like spacing.

This is absolutely not to disparage Inform7. I don’t think Dialog and Inform7 have the same core target audiences in mind. But they are both, in quite different ways, absolutely fascinating experiments in language design.

(In the end, language aesthetics is as difficult to write about as any other sort of aesthetics. Why do I find Lisp and Dialog “satisfying”, and Javascript “ugly”? I can explain my preferences in terms of a penchant for clean regularity, but I wouldn’t pretend they are more than my preferences. I mean, some people like the look of Perl, or C++. De gustibus.)

2 Likes

Hi Jeff.

Here’s the full source of Planner for Dialog (very very alpha and at least one known bug: I’m reworking the second version already to be a little more in line with the Dialog stdlib, and also trying to get to an actual game NPC performing the actions, since I7 comes with its standard library wired up for ‘actors’ and Dialog doesn’t. I actually like that Dialog doesn’t, because NPC systems are often very to taste, so I’m happy to brew my own). But it at least works in terms of generating actions.

http://natecull.org/wordpress/wp-content/uploads/2019/06/planner.r1.dg_.txt

This has more comments so it might be easier to follow and compare. Here’s the full source of the Inform 7 version (two files worth). Not quite all of Basic Plans are in Planner.r1.dg , but I think you can see the difference.

https://web.archive.org/web/20140911222205/http://inform7.com:80/extensions/Nate%20Cull/Planner/source.txt

https://web.archive.org/web/20140911222214/http://inform7.com/extensions/Nate%20Cull/Basic%20Plans/source.txt

There are a few differences between the Inform 7 version and the Dialog version. Inform 7 has no lists so I had to use a table plus a bunch of global variables, so the algorithm is a little different. The I7 version loops, while the Dialog version recurses. To implement the occurs check, I used a kind of trick in the Inform 6 / 7 version of checking backwards through a table with integer pointer fields; in Dialog, I use structure sharing of cons cells. This means that ‘Goals’ now are often replaced with ‘Purposes’ (lists of goals) which are more flexible - and this was something I just couldn’t even do in Inform 7, because of its strict typing. Here it was literally just a couple seconds change.

A second change I made - again, a spur-of-the-moment thing because I now had true untyped lists - is changing the goal for ‘to be in a room’ from [$Actor #in $Room] to just [#in $Room]. Removing the $Actor from the goal like this immediately makes the plan code simpler because I don’t need to specify the Actor twice, and in some cases I don’t need to mention it at all. Simplifications like this, again, are much harder in the Inform 6 or 7 versions which have no lists and have to rely on hard-coded global variables. More to the point - I find that the functional/logical style just lends itself to exploratory programming, where I can try different approaches and change them quickly. The Inform 7 style is much slower and harder to change anything because it’s so much work. (Not so much the rules themselves but all the infrastructure around Rulebooks, Named Rules, Kinds, Types, Values That Vary). The preferred style is to write very long and wordy sentences, which might be normal for a Cambridge maths or literature department but really isn’t for many other people.

I agree that recursive loops, in both Lisp/Scheme and Prolog, can be very hard to wrap our heads around if we’re used to imperative loops. I find that myself, as I don’t write that much functional code, which is why being able to write long wordy predicates helps me get straight what it is that I’m doing. The looping construct I’m using is the oldschool Prolog ‘accumulator’ trick (which is why I use the word ‘accumulates’)… it might be able to be made clearer using higher-order programming and closures, perhaps. It’s essentially a functional ‘fold’ operation.

Graham Nelson certainly did a great service by first making it possible to write for the Z-machine at all, and then re-introducing the world to the benefits of the rules-based approach to programming. But Inform 7 very quickly hits a complexity plateau, especially if you’re trying to do anything even a little ‘meta’, in the spirit of the original 1970s AI work from which Zork appears. Under its rules-based hood, I7 is a very old-fashioned 1950s COBOL-style imperative language with global variables and an attempt at using English sentences - much more like coding business data processing on punch cards than AI. And AI was always the ‘fun stuff’ I wanted to get to.

2 Likes

Inform 7 does have lists… they were introduced in 5J39 in Dec 2007. Your planner extension is dated to 2008, but I guess version one was from before Dec 2007?

I7 is multi-paradigm, and does let you write functional code, though most authors don’t. I7’s library is quite wordy, but you can write I7 in a very concise style, if you’re willing to write some aliases for some of the functions.

This isn’t to say that Dialog wouldn’t be much better than I7 at many things, but that I’m not sure your comparison here is very fair.

Nope, I checked this at the time, unless it’s changed since. The lists that Inform 7 introduced in 2007 are strictly typed lists. Not true untyped Lisp lists. Every element in an I7 list must be exactly of the same type - which makes it utterly impossible to represent actions or goals, as those have elements of different types. Or to do anything else in the AI or list processing field.

If the strict typing requirement on lists has been relaxed since 2007, that would help, but there are so many other elements in the I7 design that just make AI techniques extremely unpleasant to use.

Okay, that could be true. But you didn’t say that you needed “Lisp lists”, just lists, which I7 does have. And you can store actions in them.

You can store ‘actions’ - which are a built-in Inform 7 type, no user-servicable components inside - but not goals. You’d have to store them as Z-machine objects, I think, which would then break all sorts of other things.

Trust me. I’ve been through this. I7 is just not your friend if you try to do anything remotely not thought about by the designer.

But, if you think you can rewrite Planner/I7 to be as concise as Planner/Dialog, go for it. It can only help. You can probably reduce the wordiness of the phrases a lot (though I wrote them like that in 2007 because wordiness appeared to be the desired Inform 7 idiomatic style). And it’s possible that the coming overhaul of I7 may add some AI-friendly features.