Dialog

You can freely change the actor, action, and nouns…

Anyways, I think you could much more easily accomplish a planner using these features of I7. You may have to structure goals differently. (I’m not saying you Nate should.) And Dialog may end up nicer to the user.

A question on user experience with Dialog, which may relate to the notion of expressivity discussed above but from a different angle. Context: I’m just playing around with Dialog for the first time, going in with as few assumptions as possible.

I type in the first program in the Dialog manual:

(program entry point)
  Testing Dialog

Compiling that, I get:

Predicate (error $ entry point) is invoked but there is no matching rule.

Even though it’s an “error”, it still compiles and I can execute the story file. So it seems to be more a warning than an error. What’s confusing to someone who doesn’t know Prolog (or Dialog) is what this means. The manual says:

A Dialog program is a list of rule definitions . A rule definition always begins at the very first column of a line

But the error is saying something about a “predicate” with no “rule.” So that suggests a “rule definition” is not a “rule.” It leaves entirely open what a predicate is. The manual starts to clarify on that latter point:

The program above contains one predicate, built from a single rule definition.

And:

The head of the rule is (program entry point), and this is also the name of the predicate. The body of the rule is some text to be printed verbatim…

Okay, so it seems I do have a rule (which is also the predicate). But the error says I have “no matching rule.”

None of this is meant in any other way as showing what someone who is new to Prolog and Dialog experiences when they try the first example provided in the manual. (This is assuming someone actually tries to compile the minimal examples versus just read them to get a flavor for the language.)

2 Likes

Thank you @Jeff_Nyman for this excellent feedback!

2 Likes

Yes, it is confusing! I don’t think it’s an error: as you point out it’s a warning (as my version at least says); which is why it compiles. And it’s not about (program entry point) but about another predicate (error $Something entry point) which it apparently expects you may need. You can silence it by defining the predicate:

(error $ entry point)
    Do nothing

According to the manual:

(error $ErrorCode entry point)
Execution restarts here when a fatal error has occurred.

In other words: this predicate is one which you can use to catch fatal errors. Since you haven’t defined any rule for it (nothing at all to do with program entry point remember, for which you have defined a rule) Dialog warns you that it’s missing. But that’s not a compile error, and though it would obviously be an error if it ended up being invoked, the particular circumstances make that OK, because if you don’t want to catch fatal errors …

(It might be better if the manual made that clearer of course …)

2 Likes

I’m as impressed by Dialog so far as everyone else, though I’ve only made a small test game and wrote a little library to automatically add reverse room connections, which was very easy to do.

Linus, do you have anything in mind for namespaces? I can see it being very easy to have name collisions if and when people start writing libraries.

2 Likes

Been wondering about that myself. I suppose a module system would complicate things quite a bit.

SWI-Prolog’s module system ( https://www.swi-prolog.org/pldoc/man?section=defmodule ) seems to be based on putting a directive in the first line. Dialog could maybe just make a directive:

(module [predicate predicate predicate...])

but the predicates would have to be full terms not just atoms eg

(narrate entering room $)

and then… we run into the problem that predicates/terms can’t be nested inside other structures, so we’d have to have lists that pretend to be predicates, and that’s starting to get a bit ugly I think?

A thought on closures:

I like closures a lot, because what classical Prolog is missing is the ability to do decent higher-order function calls, which makes a lot of list and structure handling operations unnecessarily complicated.

But I think we need at least two parameters in closures to be able to implement ‘map’ and ‘reduce’ (we can maybe use (collect… into) for map, but there’s no easy way to get reduce I think?

Is there some easy and visually clean way to define closures by listing their free variables, rather than having a single hard-coded variable name? Can we put a closure into a list?

(query [$A $B {(do something to $A giving $B)} #foo #bar)

At this point we’re maybe breaking the core syntax too much? But since we already do have closures, it would be nice to have them take several variables.

Edit: A very dumb suggestion:

Just allocate variables as ‘parameters’ in order of their appearance in the conjunction. Eg:

(query {(do something to $A giving $B)} #foo #bar)

binds #foo to $A and #bar to $B. Obviously that gets messed up for

(query {(do something to $A giving $C} (something else $C $B)} #foo #bar)

where we don’t want to expose $C outside the closure.

So a slightly less dumb suggestion: A pseudo-predicate ‘var’ that just defines variable order:

(query { (var $A $B) (do something to $A giving $C) (something else $C $B)} #foo #bar)

binds #foo to $A and #bar to $B because that’s the order they appear in the (var) pseudo-predicate.

A conjunction that’s just called in the normal course of evaluation is treated as if it were wrapped in (query {conjunction} $ $ $ $ $…) So (var $A $B) if there are no parameters just unifies $A and $B with unknowns, ie, does nothing. It only tries to unify if there are indeed values passed in a (query $ $…) called.

Dumb… but would it work?

Edit: An even dumber idea, which might provide both closures and module/namespacess in one, would be to introduce another pseudo-predicate, say ‘def’, such that

(def (head) body...)

represents a rule definition, as a predicate call. (We would have to relax the ‘no nested predicates’ requirement for this one pseudo-predicate).

A call to (def) always succeeds, and does nothing UNLESS the enclosing closure is querie via (query). If it’s queried, then the arguments are matched against the heads and the matching body/bodies executed, exactly like a normal query. This would let us define multiple predicates, or one predicate in multiple rules, and make the top-level program not a special case but just an ordinary conjunction, with the hanging indent thing just being syntax sugar for (def …)

It’s still not quite baked, I think, because we would want a variable to be bound to the closure that represents a module, but unlike Lisp/Scheme, Prolog variable bindings don’t inherit - they only belong to a single rule definition.There’s no recursive lexical scope. So it would become very cumbersome to constantly have to do one predicate call to resolve a name to a closure, and then another one to query it. Also, we don’t have a mechanism to store a closure into a dynamic predicate.

Hi!

I’ve become very interested in Dialog in the last couple of weeks. But it treats the Cyrillic characters wrongly…
Z-machine doesn’t compile at all, though I use the Å-machine. And it has its own problems. For what I’ve noticed so far: (uppercase) predicate doesn’t seem to work and several characters of Cyrillic alphabet (ф, щ, э, В, Л, П, maybe more), as well as Russian quotation marks («», „“), convert to “??”

Is there any way to bypass these issues?

1 Like

IIRC, the Z-machine support of non-ASCII charset is via switches on the compiler side, e.g. inform -C[0-9] and dialogc (still ?) has no support for this.

HTH and

Best regards from Italy,
dott. Piergiorgio.

1 Like

Support for cyrillic isn’t implemented yet, but I’ll get right to it. Now that I know that somebody would like to use it, this feature has a much higher priority, so thanks for posting!

1 Like

This response comes a bit late, but you bring up so many interesting points that it has taken me a while to digest everything. First of all, of course, thank you very much for the delightfully specific praise! These design decisions are the result of much deliberation and iteration, and the language went through a lot of drastic transformations before going public. But none of that is described in the manual, which of course tries to describe what the language is, rather than what it isn’t. So it’s very satisfying when someone “gets it”, and provides accurate context for these design decisions in their own words.

This is an interesting idea, and not necessarily all that expensive to implement. The compiler could figure out statically which now-statements might get invoked during such a “dry run”, and add the necessary conditional instructions. But, well, this is not a feature to add carelessly. If it is possible to disable all side-effects dynamically, it might become tempting to put side-effects all over the library code, and expect the caller to be careful. I think that might lead to code that is harder to read and debug. But it’s an intriguing idea, and I’ll probably keep it at the back of my mind.

Closures provide some of the functionality of passing around prolog terms, but it’s not possible to build source-code structures from scratch, as data, and then execute them. That would have to be done using lists somehow. But I’d like to see a concrete example of where it would be useful in a story authoring context, before complicating the language any further.

This is a good point. Certainly, well-written I7 code (at least when it deals with concrete objects in the game world) can convey the purpose of the code to the reader in a very efficient, natural manner, even if the reader is new to the language. That is one of the big strengths of I7. But consider the question: Why are we reading the source code? Sometimes, indeed, we do it in order to glean the original intent of the programmer. But quite often, we read code because we’re trying to understand why it doesn’t work, or why it doesn’t behave in the way that we expected. In those situations, I find that it is immensely helpful to be able to see what the code actually does instead of what the programmer thought it would do. And the more we learn about what the programmer intended, the more we tend to see the code through the eyes of the programmer, and therefore miss the bug for the same reason that the programmer missed the bug. I’m not trying to say that we should design programming languages to be cryptic. Clarity is vital, but it should be clarity about what the program actually does, formally, when you run it, rather than clarity about what the programmer was attempting to do. In my personal, possibly controversial opinion.

This is a good point. Looking at @natecull’s subsequent suggestion, I don’t see a need to put predicates into lists. One could instead do:

(public (my public predicate $))
(public (my other public predicate $))

Or one could make everything public by default, and have a private meta-declaration. But perhaps an even better approach would be to reserve a special syntax for private predicate names, e.g. a colon as the very first character. Such predicate names would be treated as local within the current source code file. Something like:

(my public predicate $X into $Y)
	(:my private predicate $X accumulating [] into $Y)

Another file could then use (:my private predicate $ accumulating $ into $), and it would be a different predicate. Cluttering predicate names with special characters may look bad, but the benefit is that the information is present where it needs to be, at the call site, and there’s little risk of forgetting to add a public/private declaration elsewhere in the file. Well, these are just idle musings for now, but perhaps in the future…

The syntax for closures went through a lot of revisions before I settled on the current one. In fact, I’ve considered several of your suggestions. But consider this:

What you do instead, right now, is this:

	(query { ($_ = [$A $B]) (do something to $A giving $C) (something else $C $B)} [#foo #bar])

I think the latter is quite readable as it is. Though, by all means, you could define the following helper predicate:

(query $Closure $X $Y)
	(query $Closure [$X $Y])

Perhaps I misunderstand you here, but it is certainly possible to put closures in dynamic predicates, because closures are just lists. However, all variables must be bound.

Hmm. Perhaps. It takes the language in an object-oriented direction, and I’m quite happy to be rid of that baggage. The feature is already there, in a sense, but the syntax is cumbersome as you’ve noted. And that will dissuade programmers from thinking in terms of black-box objects and classes, and encourage white-box modelling with rules.

2 Likes

@lft is there a plan to release the source of Tethered as a larger example project for Dialog?

On a related note, I’d like to also voice my support for publicly hosted source on GitHub/Gitlab. No rush, of course.

2 Likes

Hi Linus. Thanks very much for your replies!

Yes, I realised after I wrote that (and with some experimenting) that we can indeed put closures into lists, so we can put them anywhere, and I forgot that you’d mentioned in the manual that we can use lists to pass multiple parameters to a closure’s single $_. So I’m going to go and try to see if I can implement map and reduce/fold using that technique, and see if it makes my core loop in Planner any clearer to read…

Some of my other ideas aren’t really about Dialog as such or the use-case of interactive fiction, but for potentially modernising and cleaning-up Prolog as a desktop or web-scale programming language. I agree that it’s really not a good idea to put nested predicates into Dialog - it clutters the language and makes it harder to tell what’s compile-time and what’s runtime. At the desktop or web scale, however, I think having ‘typed’ expressions (in a very loose sense) like classical Prolog’s terms, which are just slightly more structure than lists, could be very helpful. But this is way out of scope for the Z-machine.

I think a lot of your instincts (eg an unhappiness with the object-oriented black-box approach vs preferring white-box rules) are very much the same as mine.

I like the idea of implementing modules by marking ‘private’ predicates! That seems like quite an elegant solution.

The idea about (collect changes) is probably also something best ignored for the Z-machine, and more my blue-sky thinking about how a completely unrelated ‘pure functional Prolog’ at desktop or web-scale might possibly leverage some of Dialog’s design decisions. Such a Prolog wouldn’t have any side effects at all, maybe… the evaluation of a predicate might just produce a data stream which would either be ignored, or captured.

yay, and here’s map:

(map $Transform over $ListA into $ListB)
	(collect $B)
		*($A is one of $ListA)
		(query $Transform [$A $B])
	(into $ListB)

called via eg:

  (map {($_ = [$A $B]) ($A times 2 into $B) } over [1 2 3] into $ListB)

Edit: also filter:

(filter $Filter over $ListA into $ListB)
	(collect $A)
		*($A is one of $ListA)
		(query $Filter $A)
	(into $ListB)
	(filter {($_ modulo 2 into 0)} over [1 2 3 4] into $BarX)

I wonder if this improves readability?

(agenda $A contains intention $P)
	(filter {($_ is an intention)} over $A into [$P|$])

vs

(agenda [$P|$] contains intention $P)
	(purpose $P is an intention)
	
(agenda [$|$Ps] contains intention $P)
	(agenda $Ps contains intention $P)

However, dgdebug doesn’t seem happy calling such a higher-order query interactively:

> (1 plus 1 into $X)
Query succeeded: (1 plus 1 into 2)
> (map {($_=[$A $B]) ($A plus 1 into $B)} over [1] into $X)
dgdebug: parse.c:932: parse_expr: Assertion `cl == predname->pred->clauses[id]' failed.
Aborted (core dumped)

But if an identical line exists in a rule, I get a different result:

(intro) rule includes:

 (map {($_=[$FooA $FooB]) ($FooA plus 1 into $FooB)} over [1] into $FooX)
  (par) $FooX

result output:
[2]

but then interactively:

> (map {($_=[$FooA $FooB]) ($FooA plus 1 into $FooB)} over [1] into $FooX)
Query succeeded: (map [0] over [1] into [])
>

Hmm. Trying to naively implement ‘reduce’, of course doesn’t work because a Prolog closure doesn’t work like a Lisp closure; the variables get bound and can’t be unbound. So the recursive loop succeeds once then fails on the second iteration.

(reduce $ from $Initial [] into $Initial)
  (reduce $Reduction from $Initial [$First|$Rest] into $Final)
  (query $Reduction [$Initial $First $Result])
  (reduce $Reduction from $Result $Rest into $Final)

This makes closures a bit less useful than I thought… it seems they can only be used once, or with backtracking, ie, with (collect… into). Is there a good way of implementing reduce through backtracking? I don’t see any obvious way of passing the accumulated value through interations…

We don’t get this problem with classical Prolog higher-order predicates because the ‘predicate’ passed is only an atom which is the predicate name, so there are no variables to get bound.

I guess there’s nothing really complex about just doing reduction loops the old-fashioned Prolog way, with hand-crafted first-order predicates and accumulators.

I haven’t decided yet. The code is messy, and doesn’t exactly showcase the language in a good way.

But I’m working on my next game, and this time I try to keep the source code neat and structured, with the intention of releasing it as a large-scale example once it is finished.

This will probably happen soon, as the number of pending issues is growing.

2 Likes

You’re right, a proper example would be better for documentation.

Sounds good.

Here is Dialog release 0g/04, library 0.27.

  • Å-machine backend and dgdebug: Improved support for Unicode characters, including case conversion.

  • Å-machine backend: Fixed a bug in the string encoding, where certain international characters ended up as “??”. This bug was reported by @cheshire. The reason why a seemingly random subset of the cyrillic alphabet was acting up was that the bug didn’t affect the first 32 non-ASCII characters encountered in the source code.

  • Rephrased “Predicate … is invoked but there is no matching rule” as “A query is made to …, but there is no matching rule definition” in the general case, and then removed the warning altogether for (error $ entry point). Added some minor clarifications to the manual. These problems were reported by @Jeff_Nyman.

  • Library: Added a new ‘(game over option)’ predicate, for adding custom options to the game over menu.

6 Likes

Here is a download link for Dialog 0g/05, library 0.28.

Library feature: Heads of noun phrases

To assist with disambiguation, it is now possible to declare certain words to be potential heads of the noun phrase for a given object. The head of a noun phrase is the main noun, such as “cap” in “the bright red bottle cap of doom”.

Thus, we might define:

#bottle
(name *)        red bottle
(dict *)        crimson decanter
(heads *)       bottle decanter

#cap
(name *)        red bottle cap
(heads *)       cap

Now, if the player types EXAMINE BOTTLE, this is unambiguously interpreted as a request to examine the bottle, not the bottle cap, because one of the heads of #bottle was given. If the player types EXAMINE RED, the game will ask if they wanted to examine the red bottle or the red bottle cap. In response to that, the answer BOTTLE is unambiguously understood as the #bottle.

The list of noun heads is only consulted to resolve ambiguities. If the player attempts to TAKE BOTTLE while holding the bottle but not the cap, for instance, then that is interpreted as a request to take the bottle cap.

Hence, authors may add (heads $) definitions as needed, on a case-by-case basis, when ambiguities turn up during playtesting.

Compiler features

  • Å-machine backend: Now obeys the -s option for stripping away internal object names (hash tags) from the final story file.

  • Compiler: The quadruple-verbose output (-vvvv) now includes a list of all words the story might print. With a bit of scripting, these can be sent to an external spell checker.

  • Debugger: Commandline option -L to disable hyperlinks in the output. This also affects (interpreter supports links).

Bugfixes

  • Compiler: Fixed a bug where (determine object $) didn’t accept integers in the input.

  • Library: If parsing fails when default actions are enabled, don’t assume that a default action was intended. Thus, “enterr car” no longer results in “I only understood you as far as wanting to examine something” (when examine is the default verb).

  • Compiler: Fixed several corner-case bugs discovered through fuzzing.

5 Likes

Dialog 0g/06, library 0.29 contains some small but significant improvements:

  • Library: In the before-rules for eating and drinking, only attempt to pick up the indicated object if it is edible or potable.
  • Library: Treat “X, tell me about Y” as “ask X about Y”.
  • Library: Fixed a bug where (describe topic $) couldn’t deal with ambiguity.
  • Compiler: Fixed a bug related to the optimization of nested disjunctions.

The compiler bug was particularly elusive and would only trigger under very specific conditions. But it could result in code that would crash the game in certain corner cases, so make sure to upgrade.

3 Likes