Plists (maps / dicts) in Dialog

Dynamic Predicates limited to two parameters got me thinking about ways to represent map-like datastructures in Dialog. (Since that’s the data structure I think most programmers would normally reach for to solve problems like the one in that post.)

Anyways, here’s a snippet of Dialog that implements “plists” – which IIUC is a common way to solve this sort of problem in Lisp, where they’ve suffered from having only lists to work with for much longer. Effectively: you have lists of even length, alternating between keys and values, and a handful of helper predicates that make manipulating those lists not extremely tedious.

Still getting the hang of Dialog, so it may not be the ideal version, but perhaps interesting still…

3 Likes

This looks quite interesting, ill definitely have to mess aroundcwitg this a bit when i get the chance. I’m curious if your first predicate can handle nested plists or if $v would onpy matvh the whole list? If it could not find a nestd $v do you thinkbit would be possible to create a predicate whoch handles noth the case where $v is nested or not nested, or would seperate predicates be needed for both cases?

As-is, looking up a key grabs the entire corresponding value. So, for example,

($plist = [#k1 100 #k2 [#k3 70]])
(plist $plist key #k2 has value $out)
$out

prints [#k3 70]. So a plist can contain a plist, and the lookup works just like it would for any other value. Sort of like a dict in python or an object in javascript, I guess.

(But of course that’s just what I happened to implement! This sort of datastructure is pretty flexible… if you’re looking for some other behaviour we can probably come up with something.)

1 Like

I’m currently in the process of porting Graham nelson’s Curses to Dialog, based on various forms of release 16’s disassembly. I needed to implement Inform 6’s tasks, which in Curses are defined as constants to reference the task name and in turn refer to an array of scores. This can be represented as a map, hence my reply in this topic.

I’ve implemented this using Dialog’s ingenious unification. Instead of using constants, the names, and thus indices, are defined as rules.

When queried, these can be quite powerful as they not only provide an index based on the rule definition, but also the name based on the index for a reverse lookup.

As an example, here’s my award rule, as well as the top 3 achievements from Curses:

(score @hamburg 1)
(score @torch 2)
(score @joshua 3)

%% The task list
(global variable (taskScores [3 7 7]))

%% The award rule, which does the lookup
(award $taskName)
	(score $taskName $index)
	(taskScores $list)
	(nth $list $index $points)
	(increase score by $points)

If the index is known, we could look up the name as well:

	(score $taskName 2)
%% $taskName is now @torch

Hope this is useful and showcases another way of implementing maps.

To modify this further, we could have sublists for $taskScores, and append to it when a task is done. Or, better yet, have a second list with the task indexes and if the list contains an index, the task is done. Of course this is outside the scope of this topic, I’m mentioning it for completeness’s sake only.

I really like how Dialog makes one think outside the box, even though when porting a reasonably huge game like Curses, it can be a bit frustrating at times :relaxed:

1 Like

Quick note: when using the library, I found a small issue with the predicate that removes a key/value pair to the plist (needed for both removing key/value pairs and updating existing key/value pairs).

%% This predicate, (plist $ without $ is $), outputs a plist with all
%% instances of the corresponding key removed.
(plist [$k $v | $rest] without $query is $result)
  (plist $rest remove $query is $rest-result)
  ($result = [$k $v | $removed])

The second plist call must use without, rather than remove, to match the predicate and run correctly.

My final predicate looks like:

%% This predicate, (plist $ without $ is $), outputs a plist with all
%% instances of the corresponding key removed.
(plist [$k $v | $rest] without $query is $result)
  (plist $rest without $query is $rest-result)
  ($result = [$k $v | $removed])

Thanks for the work on this @bkirwi! I needed to keep a simple global variable (store all the discovered rooms and visible exits) for a game I’m working on, and this made it trivial to store, update, and read that data.

1 Like

Well spotted - thanks for the correction! (And glad this idea has been useful… I haven’t actually needed it for a game of my own yet.)

I’ve updated the original post with your suggestion, plus a few other stylistic changes I feel compelled to perform now that I’ve worked with Dialog for longer. (Mostly getting more use out of unification and multiple rule heads.) That way anyone else who stumbles on this post won’t need to scroll down to get the correct code.

2 Likes