22. Advanced Phrases

Writing with Inform

WI §22.1 A review of kinds

Most of the time, what's created in an Inform source text will have a name which can be used as a value - sometimes openly so, sometimes not. In this book, we haven't gone out of our way to make that point, because there was no real need to do so. It's possible to make heavy use of rulebooks and write large-scale Inform projects without ever needing to use a rulebook's name as a value in its own right, for example. But if we want to create sophisticated extensions to Inform, or to use modern techniques such as functional and generic programming, we need to be fluent in the language of kinds.

Inform's language of kinds has four ingredients: base kinds, constructions, kind variables and kinds of kinds.

1. Base kinds. Inform provides the following base kinds for values:

object, number, real number, time, truth state, text, snippet, Unicode character, action, scene, table name, equation name, use option, action name, figure name, sound name, external file, rulebook outcome, parser error

together with a few others, such as "response" and "verb", to do with linguistic features.

And Inform allows us to create new base kinds either by making more specialised kinds of object:

A geographical layout is a kind of object.
A marmoset is a kind of animal.

Or by making new enumerations or arithmetical kinds:

Distance is a kind of value. 10km specifies a distance.
Colour is a kind of value. Red, green and blue are colours.

2. Constructions. These are ways to make new kinds from existing ones. The construction most often used is "list of…". For any kind K, there is a kind called list of K. So the range of possible kinds in Inform is actually infinite, because:

number
list of numbers
list of lists of numbers
list of lists of lists of numbers
...

are all different from each other. Inform has nine constructions, as follows:

list of K
description of K
relation of K to L
K based rule producing L
K based rulebook producing L
activity on K
phrase K -> L
K valued property
K valued table column

Some of these have appeared in previous chapters, but in abbreviated form. For example, "rulebook" abbreviates "action based rulebook producing nothing", and "either/or property" is a synonym for "truth state valued property". The kinds of descriptions and phrases haven't been covered yet, but are coming up in the sections following.

These constructions can of course be combined:

phrase (relation of numbers to colours, number) -> list of colours

Brackets can be used to clarify matters:

phrase (phrase number -> (phrase number -> number)) -> nothing

Nothing will make that a simple idea, but it's unambiguous and can be puzzled out with practice.

3. Variables. In a way, that's everything: there are just base kinds and constructions on them, and those construct every possible kind in Inform. But the language we use to describe kinds is larger than that, because it allows us to describe many kinds at once, in the same way that Inform reads the word "something" as applying to many objects at once, not as a single object.

Kind variables will be covered later in the chapter, but the idea is that:

To hunt for (needle - value of kind K) in (haystack - list of Ks): ...

allows us to describe the kinds acceptable in a phrase so that a wide range of possibilities are allowed. The above matches both:

hunt for 4 in { 2, 3, 4, 5 };
hunt for "fish" in { "lemon sauce", "fish", "garden peas" };

The letter K in the definition stood for any kind; in the first use of "hunt" here, K turned out to be "number", and in the second it was "text". On the other hand Inform would reject:

hunt for 4 in { containment relation, support relation };

because there is no kind K which can make this match the definition.

There are potentially 26 kind variables, A to Z, though it's customary to use them in the order K, L, M, …, and it's very rare to need more than two at a time.

4. Kinds of kind. Inform understands several names which look as if they are kinds, but actually aren't:

value, arithmetic value, enumerated value, sayable value

(Again, these are built in to Inform.) They are not kinds because they're just too loose and vague. Instead, they can be used in phrase definitions to match against multiple possibilities:

To announce (X - sayable value): say "I declare that [X] has arrived."

This makes "announce X" work for any value X which can be said. All the same, "sayable value" is not a kind. It could never be safe for this to be the kind of a variable, because Inform would never know what could be done with the contents (except that it could be printed out).

5. Secret inner workings. There isn't a fifth ingredient, but if there were, it would be a handful of names used in matching some of the core built-in phrases of Inform which have so-called inline i6 definitions. These are not intended for anyone else to use, and are just an internal convenience; they aren't publicly documented and might change without notice. They don't describe kinds at all, because they tell the matcher to look for something else instead. For instance, there's one called "condition", which means "match a condition rather than a value". They appear in red ink in the Phrasebook index.

WI §22.2 Descriptions as values

In the chapter on Descriptions, we saw that a description can be any source text which describes one or more objects: it might be as simple as "the Corn Market", or as complicated as "open containers which are in dark rooms". More or less the only restriction is that it must be unambiguous as to what counts and what does not: "three containers" is ambiguous as a description because it does not say which three.

We've now seen several interesting tricks with descriptions. In fact, if D is a description, then

say "You gaze mournfully at [the list of D].";
let the tally be the number of D;
let the surprise prize be a random D;
repeat with item running through D:
   ...

are all standard things to do. These examples make it look as if it must be possible to define phrases which act on descriptions, and in fact it is, because a description can be a value in itself. For example,

even numbers
open containers which are in dark rooms

are values of kind "description of numbers" and "description of objects" respectively. In general, if K is any kind then "description of K" is also a kind. Here is how we might make use of that:

To enumerate (collection - a description of objects):
   repeat with the item running through the collection:
      say "-- [The item]."

This makes "enumerate lighted rooms" run off a list of lighted rooms in a textual format different from the standard one produced by "say the list of lighted rooms". Inside the definition, "collection" can be used wherever a description might be used: here, for instance, we use it as the range for the repeat loop. (That's only possible because the range is limited in size: Inform wouldn't have allowed us to range through, say, all texts.)

Purely as a convenience, we can also write "member of" or "members of" in this context. For instance, in the enumerate definition, it would have been just as good to write "…running through the members of the collection…" in the repeat. (Similarly, we could write "number of members of the collection" or "a random member of the collection", which looks grammatically tidier than writing "number of the collection" or "random of the collection" - though in fact both of these do work.)

Finally, it's sometimes useful in an abstract situation to test

if (value) matches (description of values):

This condition is true if the value matches the description; the kinds must be compatible, or Inform will issue a problem message. There is no point using this for cases where the description is given explicitly:

if 4 matches even numbers, ...

because it is easier to write just:

if 4 is an even number, ...

So this condition is only useful when the description is stored in some variable, and its identity is not known.

Example

438.
Curare
A phrase that chooses and names the least-recently selected item from the collection given, allowing the text to cycle semi-randomly through a group of objects.

WI §22.3 Phrases as values

Given any two kinds K and L, the kind "phrase K -> L" is now a kind. (This is meant to look like a mathematical function arrow.) For example, the phrase defined by

To decide which number is the square of (N - a number): ...

has the kind "phrase number -> number". Brackets and commas are used if the phrase combines several values, so

To decide which text is (T - text) repeated (N - a number) times: ...

has the kind "phrase (text, number) -> text". The word "nothing" is used if there are no values in, or no value out - thus

To decide which number is the magic target: ...

has kind "phrase nothing -> number", and

To dig (eastward - length) by (northward - length): ...

has the kind "phrase (length, length) -> nothing".

But how are we to get at these values? The answer is that we need to give a phrase a name in order to do so. For example:

To decide what number is double (N - a number) (this is doubling):
   decide on N plus N.

This is the same syntax used to name rules, and the idea is the same. If we try "showme doubling", the result is

phrase number -> number: doubling

The main thing we want to do with a phrase is to apply it. So:

showme doubling applied to 2;

produces

"doubling applied to 2" = number: 4

There are versions of "applied to" for phrases applied to 0 to 3 values:

(phrase nothing -> value) appliedvalue

This phrase produces the result of applying the given phrase, which must be one which takes no values itself.

(phrase value -> value) applied to (value)value

This phrase produces the result of applying the given phrase, which must be one which takes one value itself.

(phrase (value, value) -> value) applied to (value) and (value)value

This phrase produces the result of applying the given phrase, which must be one which takes two values itself.

(phrase (value, value, value) -> value) applied to (value) and (value) and (value)value

This phrase produces the result of applying the given phrase, which must be one which takes three values itself.

So for example:

F applied
F applied to V
F applied to V and W
F applied to V and W and X

For phrases which do not produce a value, we use "apply":

apply (phrase nothing -> nothing)

This phrase causes the given phrase to be applied. It must be one which takes no values itself.

apply (phrase value -> nothing) to (value)

This phrase causes the given phrase to be applied. It must be one which takes one value itself.

apply (phrase (value, value) -> nothing) to (value) and (value)

This phrase causes the given phrase to be applied. It must be one which takes two values itself.

apply (phrase (value, value, value) -> nothing) to (value) and (value) and (value)

This phrase causes the given phrase to be applied. It must be one which takes three values itself.

Thus:

apply F;
apply F to V;
apply F to V and W;
apply F to V and W and X;

WI §22.4 Default values for phrase kinds

The default value for "phrase K -> nothing" is a phrase which does nothing. For example, if we write:

let S be a phrase numbers -> nothing;

then S is created holding the default phrase numbers -> nothing, and if we then try it out with:

apply S to 17;

we will indeed find that nothing happens.

The default value for "phrase K -> L" is a phrase which, no matter what value of K it applies to, always produces the default value of L. (It's a sort of equivalent of the zero function in mathematics - indeed that's exactly what it is, if L is "number".) So:

let Q be a phrase numbers -> times;
showme Q;
showme Q applied to 4;
showme Q applied to -7;

produces:

"q" = phrase number -> time: default value of phrase number -> time
"q applied to 4" = time: 9:00 am
"q applied to -7" = time: 9:00 am

Here Q is set to the default phrase because we didn't give it any other value; it has the name we might expect ("default value of phrase number -> time") and it works as advertised, producing 9:00 am no matter what number is fed in.

More ambitiously, and supposing that we have a kind called "colour" whose first possible value is "red":

let R be a phrase numbers -> (phrase numbers -> colours);
showme R;
showme R applied to 3;
showme (R applied to 3) applied to 2;

produces:

"r" = phrase number -> (phrase number -> colour): default value of phrase
number -> (phrase number -> colour)
"r applied to 3" = phrase number -> colour: default value of phrase number
-> colour
"( r applied to 3 ) applied to 2" = colour: red

WI §22.5 Map, filter and reduce

When a mass of computations has to be done, the traditional approach is to work through them in a "repeat" loop. One modern alternative, growing in popularity, is to form a list of inputs; then apply the same computation to each input in turn to form a list of results (this is called "mapping"); throw out any bad or unwanted results ("filtering"); and then combine the surviving results into a single composite answer ("reducing", though some programming languages call this "folding" or "accumulation"; it's a much-reinvented idea).

Inform provides all three of these fundamental list-processing operations. There is no special term for a "map", because Inform treats it as another case of "applied to".

(phrase value -> value) applied to (list of values)value

This phrase takes the list, applies the phrase to each entry in the list, and forms a new list of the result. Example:

To decide what number is double (N - a number) (this is doubling):
   decide on N plus N.

Then "doubling applied to 2" produces 4, by the simpler definition of "applied to", but also:

doubling applied to {2, 3, 4}

produces the list {4, 6, 8}.

More divertingly, suppose we define:

To decide what text is the longhand form of (N - a number)
   (this is spelling out):
   decide on "[N in words]".
To decide what text is the consonant form of (T - text)
   (this is txtng):
   replace the regular expression "<aeiou>" in T with "";
   decide on T.

Then we can write a chain of three maps in succession:

txtng applied to spelling out applied to doubling applied to {3, 8, 4, 19, 7}

to produce the value {"sx", "sxtn", "ght", "thrty-ght", "frtn"}.

Next, filtering. Here we make use of descriptions, in order to say what values will be allowed through the filter. So:

filter to (description of values) of (list of values)value

This phrase produces a new list which is a thinner version of the one given, so that it contains only those values which match the description given. Example:

filter to even numbers of {3, 8, 4, 19, 7}

produces {8, 4}, with the values 3, 19, and 7 failing to make it through. A sufficiently fine filter may well thin out a list to a single entry, or even no entries at all, but the result is always a list.

To get the full effect of filtering, we probably need to define an adjective or two. For example:

Definition: a text (called T) is lengthy if the number of characters in it is greater than 6.

We can then write, for example:

let L be the filter to lengthy texts of spelling out applied to {15, 2, 20, 29, -4};
showme L;

which produces the list {"fifteen", "twenty-nine", "minus four"}.

Lastly, reduction. In order to combine a whole list of values, we need a phrase to combine any two. Here are some samples:

To decide what number is the larger of (N - number) and (M - number)
   (this is maximization):
   if N > M, decide on N;
   decide on M.
To decide what text is the concatenation of (X - text) and (Y - text)
   (this is concatenation):
   decide on "[X][Y]".

And here are some sample reductions:

let X be the maximization reduction of {3, 8, 4, 19, 7};
let Y be the concatenation reduction of txtng applied to spelling out
   applied to doubling applied to {3, 8, 4, 19, 7};

sets X to 19, the highest of the values, and Y to the text "sxsxtnghtthrty-ghtfrtn". In each case a list has been reduced to a single value which somehow combines the contents.

(phrase (value, value) -> value) reduction of (list of values)value

This phrase works through the list and accumulates the values in it, using the phrase supplied. Example: if we have

To decide what number is the sum of (N - number) and (M - number)
   (this is summing):
   decide on N + M.

then the summing reduction of {3, 8, 4, 19, 7} is the number 41, obtained by

(((3 + 8) + 4) + 19) + 7

so that the summing phrase has been used four times.

Is map/filter/reduce always a good idea? Devotees point out that almost any computation can be thought of in this way, and in systems where the work has to be distributed around multiple processors it can be a very powerful tool. (There are programming languages without loops where it's essentially the only tool.) At its best, it reads very elegantly: one assembles all of the tools needed - definitions of doubling, lengthy, spelling out, concatenation and so on - and then each actual task is expressed in a single line at the end.

On the other hand, there are also times when this is a needlessly complicated disguise for what could more easily be done with a "repeat" loop, and also more efficiently since assembling and dismantling lists in memory does take some overhead time. So these list operations are not a panacea, but it's good to have them available.

WI §22.6 Generic phrases

The following looks quite innocent:

To say (V - value) twice: say "[V]. [V], I say!"

It's clear at a glance what this is intended to do, but at a second glance things aren't so straightforward. "Value" is not itself a kind - it's too big and unspecific. For instance, if we were to allow a variable to be just "a value", we could freely set it to 12 one minute and to "dahlias" the next, and such a variable would be dangerous since we would never know what could safely be done with its contents. A phrase like this one is called "generic", because it's not so much a single, actual phrase as a recipe to make phrases. (Inform automatically works out which kinds we need the phrase for, and creates a version of the phrase for those kinds.)

So "value" is not a kind, but a kind of kind. Inform has several of these:

value, arithmetic value, enumerated value, sayable value

These act as ways to say "a value of any kind matching this can go here". For example, "value" is a way to say "any kind at all"; "arithmetic value" is any kind which arithmetic can be performed on (any kind with the little calculator icon in the Arithmetic part of the Kinds index); and so on. If we write:

To double (V - arithmetic value): say "[V times 2]."

the restriction to "arithmetic value" means that although "double 3", "double 6 kg", etc., would be matched, "double the Entire Game" would not - you can't perform arithmetic on scenes. Similarly, it would have been tidier to write:

To say (V - sayable value) twice: say "[V]. [V], I say!"

because then Inform will make it clearer why "say X twice" won't work if X is one of those rare values which it can't say (an activity, for instance).

The Kinds index shows which kinds match against which of these "kinds of kind". For instance, it shows that "time"

Matches: value, arithmetic value, sayable value

which means that time is something we can do arithmetic on, and can say.

WI §22.7 Kind variables

The examples of generic phrases in the previous section were really only toy examples. Suppose we want a phrase which will take any arithmetic value and triple it. We could do something like this:

To triple (V - arithmetic value): say "[V times 3]."

But this only prints the answer. Suppose we want to be given the value back, instead: how can we write the phrase? The trouble is that, not knowing the kind of V, we can't say what kind will be produced. We need a way of saying "the same kind comes out as went in". Inform expresses that using kind variables:

To decide which K is triple (original - arithmetic value of kind K):
   decide on 3 times the original.

Here, K stands for any kind which matches "arithmetic value". Inform supports exactly 26 of these symbols, which are written A to Z, but it's customary to use K and L. (They can be written in the plural if we like: e.g., "list of Ks". But they must always use a capital letter: "list of k" is not allowed.)

Each symbol we use has to be declared in exactly one of the bracketed ingredients for the phrase - here, the declaration is "arithmetic value of kind K". That creates K and says that it has to be arithmetic; if we'd just said "value of kind K", it could have been anything. (Alternatively, we could use any of the kinds of kind in the previous section.)

For a more ambitious example, here is one way to define the mapping operation described earlier in the chapter:

To decide what list of L is (function - phrase K -> value of kind L)
   applied to (original list - list of values of kind K):
   let the result be a list of Ls;
   repeat with item running through the original list:
      let the mapped item be the function applied to the item;
      add the mapped item to the result;
   decide on the result.

Here we need two symbols to explain the complicated way that the values going in and out have to match up to each other. Note also the way that the temporary variable "result" is created:

let the result be a list of Ls;

Ordinarily, of course, "L" is not a kind. But within the body of a phrase definition, it means whatever kind L matched against.

When a symbol occurs several times in the same definition, subtle differences can arise according to which appearance is the declaration. These are not quite the same:

To hunt for (V - value of kind K) in (L - list of Ks): ...
To hunt for (V - K) in (L - list of values of kind K): ...

The difference arises - though very rarely - if V has some different kind compared to the list entries, but which can be used as if it were of that kind. For example,

hunt for the player's command in {"take all", "wait"};

Here V is a snippet, but L is a list of texts; and a snippet can be used in place of a text, but not vice versa. So this will match the second definition, because K is set to "text", but it won't match the first, where K is set to "snippet".

WI §22.8 Matching the names of kinds

Sometimes a phrase needs to know what kind it's to work on, but isn't going to be given any particular value of it. For example:

To assay (name of kind of value K):
   repeat with item running through Ks:
      say "There's [item].";
   say "But the default is [default value of K].";

Note that there's no hyphen, and no name for the bracketed token - it only sets K. We can then have, say:

assay colours;
assay vehicles;

But "assay texts" would throw a problem message, because we can't repeat through all possible texts. For a different reason,

assay open doors;

would not be allowed - "open doors" is a description which applies to some doors and not others; it isn't a kind. It would make no sense to talk about "default value of open door", for example.

WI §22.9 In what order?

Recall the definition:

To slam shut (box - an open container): say "With great panache, you slam shut [the box].".

Suppose we then try to "slam shut the wall safe" at a time during play when the wall safe is already closed. An error message will then be printed during play, since there must be a mistake in the design. The combination of checking both when Inform builds the story file and then continuously when the story file is played guarantees that, in all cases, a varying item such as "box" in the definition of "To slam shut (box - open container)" always satisfies the condition laid down.

Instead suppose we also have the following definition:

To slam shut (box - a container): say "You are unable to slam shut [the box], which is already closed.".

We now have two definitions of "slam shut". Sometimes the box it's applied to will be closed, in which case only the second definition fits, and will be the one used. But if the box is open, both definitions fit. Which will happen? The rule is:

1. A narrower condition takes precedence over a broader one;
2. If neither condition is narrower than the other, then whichever phrase was defined later in the source code is the one taking precedence;
3. Except that if the phrase is being used in the definition of phrase P, then P is always last in precedence order, so that recursion is always the very last possibility considered. This allows more specific or later definitions to make use of less specific or earlier ones in a natural way.

Rule 1 ensures that a definition involving "open container" takes priority over one which merely involves "container", for instance.

And therefore when the box is open, it's the more specific phrase to do with open containers which is invoked: so, with great panache, the box is slammed shut.

On the other hand, neither of these patterns is narrower than the other:

To describe (something - transparent): ...
To describe (something - container): ...

Some containers are transparent, some not; some transparent things are containers, some not. Rule 1 therefore does not apply, so it is the later of the two phrases which takes effect.

WI §22.10 Ambiguities

Another possible ambiguity occurs when a phrase might match two lexically different possibilities using the same words.

say the dishcloth;

could be construed as a usage of either of these cases:

say the (something - a thing)
say (something - a thing)

These of course have different effects - one produces the name with a definite article, the other just the name, so the difference is important.

The rule here is that whichever possibility contains the most words, in this case "say the (…)", takes precedence, because it's assumed to be a more specific form of the less wordy version.