So this is the culmination of about 6 weeks of work. Starting with JSON, then Collections, I have been working on extensions that provide new kinds of values for Inform 7. This work has reached its final form in the new Data Structures extension. Unlike the previous two, Data Structures also requires you to install two .i6t template files:
That’s a slight hassle, but brings the big advantage of not needing to manually deallocate values. Instead we can take advantage of Inform’s built in reference counting/garbage collection.
You can leave those files in the I6T folder even if you do not use Data Structures in some of your projects; the kinds will still be present in the Index, but as long as you don’t try to use them they will have no effect.
This extension tries to be safe by default, so the return values of some phrases are Options or Results. These kinds wrap an optional return value; for Options you either will have a return value or none, for Results, either a return value or an error message. You must then check what is returned from these phrases and make sure you account for the possibility of failure. There are also unsafe “unchecked” phrases which you can use when you are sure that the phrase will be successful, but this is discouraged. In fact it will often be not only safer but also more performant to use the safe variations.
For example, you might think of checking if a map has a value and extracting it like this:
if fruit varieties has key "apple": let apple name be get key "apple" of fruit varieties unchecked;
But when you do this the code actually searches through the map twice: first to check if the key exists, and then to extract the value. It is better to just use the safe variant which returns an option:
let result be get key "apple" of fruit varieties; if result is some let apple name be the value: ...
And in fact you can combine these into one statement:
if get key "apple" of fruit varieties is some let apple name be the value: ...
When a phrase may fail there is sometimes a phrase variant that lets you specify a backup value:
let apple name be get key "apple" of fruit varieties or "Royal Gala";
An any stores a value and its kind; the kind cannot be determined at compile time, but can be read at run time. These are useful for when you want to store multiple kinds of values in one list or map, or for when you don’t know what kind some data might be.
When play begins: let apple be "Royal Gala" as an any; if kind of apple is a text: say "[apple] is a text[line break]"; if apple as a text is okay let apple name be the value: say "Apple variety: [apple name][line break]"; let year be apple as a number or 2022;
A closure preserves the state of a phrase so that it can be resumed at a later time. They are still experimental, and do not yet support block value local variables.
When play begins: let C1 be a new closure number -> number; ignore the result of generate test closure with C1; say "Running:[line break][C1 applied to 10][line break]"; say "Running:[line break][C1 applied to 100][line break]"; To decide what number is generate test closure with (C - closure number -> number): say "Closure setup[line break]"; let N1 be 1; initialise C with parameter N2; say "Resumed closure[line break]"; say "N1: [N1][line break]"; increment N1; say "N2: [N2][line break]"; update all local variables of C; decide on 20;
A couple is a 2-tuple, grouping two values of any kind. Couples are useful for when you need to return two values of different kinds from a phrase.
To decide what couple of person and number is the person evaluation: decide on yourself and 1234 as a couple; When play begins: let result be the person evaluation; say "Person: [first value of result][line break]Evaluation: [second value of result][line break]";
Maps store key-value pairs. Each map has a set kind for its keys and another set kind for its values, but if you need to store heterogenous keys or values you can make a map using anys.
When play begins: let data be a map of text to any; set key "player" of data to yourself; set key "score" of data to 0; set key "action" of data to the jumping action; if get key "score" of data is some let score be the value: say "Starting score: [score][line break]"; let temperature be get key "temp" of data or 23 as an any;
Null values are occasionally useful; they are needed for parsing JSON, and can also be used for a promise that indicates when it is finished but has no actual resulting value.
An optional value, either nothing, or a value of a specific kind.
When play begins: let O1 be "Hello" as an option; let O2 be a text none option; if O1 is some let message be the value: say "Message: [message][line break]"; let second message be value of O2 or "Goodbye";
A promise represents a value which is yet to be determined, and holds a list of code hooks to run when it has been resolved. Promises are still somewhat experimental.
Jump promise is a person promise that varies. When play begins: now jump promise is a new person promise; attach receive the jumper to jump promise; To receive the jumper (P - person) (this is receive the jumper): say "[P] jumped!"; After jumping: ignore the result of resolve jump promise with the player;
A result contains either a wrapped value or an error message text.
When play begins: let R1 be 1234 as a result; if R1 is okay let score be the value: say "Score: [score][line break]"; let R2 be a number error result with message "Oops!"; if R2 is okay let score be the value: say "Score: [score][line break]"; otherwise if R2 is an error let message be the error message: say "Error! [message][line break]";
I need to complete the unit tests for this extension, which will no doubt expose more bugs. Closures still need a lot of work, to support block value variables, and also to support more parameter options; unfortunately each number of parameters needs to be manually implemented. I will also work more on Promises: you should be able to add a closure as a promise handler, and I’ll also consider adding ways of combining promises (probably based on the JS Promise API.)