Inform 7 for Programmers/Part 2

From IFWiki

Patterned Procedures

A function's "name" in Inform 7 is not an exact match of a single identifier, but a very simple textual pattern. It's easier to show this than explain, so, for functions that do not return a value:

To plainly greet (friend - a person):
say "Hi [friend]."
To ponder/mull over/-- (good point - a thing) for (awhile - a time) as (ponderer - a person):
say "[Ponderer] sits back in [his-her] chair for about [awhile]. 'Hm, [good point] is a very good point, sir.'"
plainly greet Dr. Muller;

(These are all equivalent:)

ponder the best idea yet for 7 minutes as Dr. Muller;
ponder over the best idea yet for 7 minutes as Dr. Muller;
mull best idea yet for 7 minutes as Muller;
mull over the best idea yet for 7 minutes as Dr. Muller;

The / forward slash divides synonyms, cannot be surrounded by spaces, and its effect ends at the first space; it's a high-precedence operator. The -- double-dash means the word(s) are optional. That is the full extent of the pattern-matching. Parameters are in parenthesis: the parameter's name precedes a hyphen and its type. Simple values are passed by-value only; objects, by-reference only. A parameter can immediately follow the "To" that begins the definition, but, two parameters cannot sit immediately side-by-side:

To (ponderer - a person) ponders/ponder/mulls/mull (good point - a thing): [ok].
To ponder for (awhile - a time) (good point - a thing): [error!].

"Let" creates a local variable. "Let" is also how we create the aggregate types like "list" and "indexed text", since they cannot be statically allocated, and the language does not allow dynamic allocation.

let X be 5;
let the typical exclamation be "That's cool!";
let the articles of clothing be the list of things worn by Bob;
let M be { the red Porsche, the pair of shoes };
let the modifiable exclamation be indexed text; [ These work... ]
let the modifiable exclamation be "That's cool!"; [ a pair. ]

"Stop" is the plain-jane return statement. It isn't used much, partly because of some other synonyms for return, and partly as fallout from the rules-based structure of the language.

A final natural-language feature cloaks a bitfield as a series of comma-separated sub-phrases:

To go hiking, into the woods or up the mountain:
if into the woods then say "Watch out for badgers.";
if up the mountain then say "Better take your compass.";
say "You go hiking."

Clever "naming" not only affords client code that is easy to read, but also creates library invocations that are easy to make a half-remembered guess at. The latter is, in practice, a wonderful time-saver. When we define functions, we should take client code readability into account. For example, we needn't add the articles in front of a parameter, because the parameter itself will eat it:

To ponder the/an/a/-- (nefarious plans - a rulebook): [ unnecessary ].
To ponder (nefarious plans - a rulebook): [ better ].

But we should explicitly add the articles if it occurs elsewhere, such as this example that pretends to understand an adjective in some cases:

To ponder the/an/a/-- foiled/new/-- (nefarious plans - a rulebook): [...].

"To" phrases tend not to be used too much, for the similar reason that methods in a OOPL tend to reduce global function use. Instead, Inform 7 has rules, grouped into rulebooks, which we'll get to shortly. To phrases do have a nicer invocation syntax and essentially unlimited arity to recommend them over rules, but they lack the flexibility of rulebooks, as we'll see below.

Functions Decide on a Value

Because functions that return a value must have those values used in a larger statement (strong-typing disallows throwing them away), their names tend to be noun phrases rather than sentences.

To decide which room is my favorite place: [...]; decide on My Bedroom.
To decide what person is brother to/of (sibling - a person): [...]; decide sibling.
To decide which object is my fabulous doodad: decide on a random thing.
ponder the best idea in my favorite place;
if the brother of the noun is not the noun, say "[Noun] has a brother, [Brother of the noun].";
if my fabulous doodad is nothing, say "I'm fresh out of fabulous.";

The keywords "decide which/what" and "is" wrap around the returned type. The function's name is only that part between "is" and the colon. The return statement is now "decide on/--". Due to strong-typing and "nothing" being implemented as an instantiation of class Object, we cannot "decide on nothing" except when deciding "what/which object is".

Boolean functions must use the slightly different "whether/if" variation, and the name lies between the "whether/if" and the colon. Since they are invoked from if statements and rule headers, their names are frequently clauses sans subordinating conjunction.

To decide whether (pants - a thing) is/are on fire:
decide on whether or not a random chance of 1 in 2 succeeds.
if the brother of the noun is on fire, say "That's gonna leave a mark.";

The phrase "whether or not" typecasts an if-condition ("a random chance of M in N succeeds") to a truth state ("yes/no"; a boolean), which can then be returned.

Say Phrases

It is so common to slightly vary some prose for a given situation that Inform specifically provides for procedures called from within a Say statement's prose. "To say" phrases are in a box separate from "To" procedures and "To Decide" functions, but otherwise work identically. They are invoked by square brackets.

Gendered pronouns are a common case, and most are built-in.

To say He-She for (P - a person):
if P is plural begin;
say "They";
if P is female, say "She";
otherwise say "He";
end if.
To say (P - a person) mulls/ponders --/over (idea - a thing): [...].
[...]; say "[He-She for Chris] glances at you[Chris ponders tar-and-feathering].";

Putting a bare object or variable name within the square brackets prints the entity's name or variable's value, respectively. This works for nearly every type in the language, though can usually be overridden like so:

To say (code - a rule): abide by the code.

That would execute the rule or rulebook from within prose, rather than printing something.

say "Chris seems to make a decision.[the formulate plans rules] But you don't know what.";

Inform has built-in a number of basic imperatives for say phrases. The docs have the full list, and the Extensions chapter has information on creating new multi-part To Say constructions.

say "He put on [if the jeans are stained]yesterday's[otherwise]his[end if] jeans.";
say "The weather was [one of]rainy[or]sunny[or]windy[at random].";

Sweet Relations

In lieu of numerical relationships, qualitative either/or relationships frequent interactive fiction. Relations disguise object-to-object 2D boolean arrays behind some of the best syntactic sugar in the language. Though a relation is declared with a single-word noun (a second place where the identifier must be a singular word), in initializations and invocations relations use the infix-like form of English verbs. Reciprocal (symmetric), non-reciprocal (asymmetric), and grouped (equivalence) relations are possible, as are synonymous and commutated forms for the verbs.

Marriage relates one person to another (called the spouse). ["to another" means a symmetric relation]
The verb to be married to implies the marriage relation.
Bob is married to Jane.
[...]; if Jane is married to Bob, [...]
Friendship relates various people to various people. [an asymmetric relation]
The verb to be friends with implies the friendship relation.
The verb to be befriended by implies the reversed friendship relation. [ defining a "reversed" syntax for asymmetric relations is useful in Descriptions ]
if the brother of the noun is married to the second noun, now the noun befriends the second noun;
repeat with associate running through every person who is friends with the brother of the noun begin;
end repeat;
Trust relates people to each other in groups. [an equivalence relation]
The verb to trust (he trusts, they trust, he trusted, it is trusted, he is trusting) implies the Trust relation.

When we define a verb like "to be X", Inform can automatically conjugate it. But if we use the "to X" form, we must supply the other five forms in parenthesis. Also, "to be X" can use multiple words: "to be X with", for example. But "to X" must be a single word. Additionally, we can define the verb as "to be able to X", which means the relation is used like "can X".

Memorability relates various people to various things.
The verb to be able to remember (he is remembered) implies the memorability relation.
Bob is a man. The time he spent in jail is a thing.
Bob can remember the time he spent in jail.

Though only an asymmetric various-to-various relation actually needs a whole 2D array, the other kinds of relations are some sort of subset of one. Any relation with a singular is implemented as a property, as are equivalence relations.

The complete list of relation types are declared using one of these phrases; a "called" parenthetical (such as the spouse, above) is allowed on any singular. person to one person...
...various people to one person... person to various people...
...various people to various people... person to another... [ one-to-one, symmetric (if A~B, then also B~A; think "commutative operator")]
...people to each other... [various-to-various, symmetric]
...people to each other in groups... [equivalence (think "if A equals B, then A also equals everything that B equals)]

Much like boolean adjectives, relations can be implemented as calculation rather than storage.

Siblinghood relates a person (called X) to a person (called Y) when X is the brother of Y or X is the sister of Y.
The verb to be a sibling of implies the siblinghood relation.

The value-checking syntax is the same, so swapping between calculation and storage is almost automatic. (Any line that explicitly sets a calculated relation will cause an error, but the code that checks the relation remains unchanged.) It is worth nothing that the relativistic relations of math are implemented this way ("The verb to be greater than implies the...") as well as almost all spatial relations (under, on, in, northeast of, etc.)

This brings us to non-object relations. Named values can take part in relations, as can numbers. But numbers, as there are an infinite variety of them, can only be part of a calculated relation, never a stored one. The types can mix and match.

But, as wonderful-looking as relations are, they are second-class citizens. We can't create a relation variable, nor return them from functions. And where we can use them is in very specific, special places. One is pathfinding:

let X be the number of steps via the acts-with relation from Kevin Bacon to Jodie Foster;
let S be the next step via the acts-with relation from Christopher Walken to Kevin Bacon;

And the other, far more powerful place to use relations, is in Descriptions (the Set-Of-Objects type) as a query.

let L be the list of spiffy people who are friends with Bob;
repeat with countrymen running through every chatty not boring person who trusts the player begin;
repeat with countrymen running through every chatty not boring person who trusts the player who is friends with a person (called the shill) who owns a thing (called riches) begin;
say "Hi [countrymen]; [shill] said you'd lend me your [riches].";
end repeat;

In the last description, each subordinate phrase applies to the main noun -- person, in this case -- not to the nouns listed in other subordinate phrases -- such as player, thing, or the second person. (Asking "who is friends with a person" is a way of asking who has friends.) We cannot insert commas or conjunctions ("and").

Rules of Thumb

Because interactive fictions are single-author artworks rather than team-designed workhorses, novelty and ease of modification trump safety and scalability. The resulting high-level design decision was for a partially rule-based paradigm over a pure procedural paradigm. Where the procedural paradigm triggers imperative code blocks by invoking that code's given name (the name + code construction is called a procedure or function), the rule-based paradigm triggers imperative code blocks by attaching situational information to it; the situational condition(s) are called the header, and the header + code construction is called a rule.

In other words, a rule encapsulates -when- its code executes. For a procedure, the information about when it executes is spread diffusely over the source code: where-ever its name appears. Rules typically don't need names because rules already know when they execute; they needn't be told so by other code.

In Inform, a colon ends the rule header and begins the imperative code block; a period or a blank line end the block. Articles can be added for readability, and frequently one of the words about/for/of/on/rule can be added as well. (Details in Section 18.6 "The Preamble of a Rule", and Example 370 "Backus-Naur form".)

A persuasion rule: [...]; [...]; [...].
Persuasion: [...].
Every turn: [...].
Every turn during the collapsing bridge scene: [...].
Report someone burning something: [...].
Instead of burning something held: say "No, you might burn yourself!".
Carry out an actor helping someone: now the noun is friends with the actor.
Report tattooing: say "You go to work on [the second noun]'s [the limb understood]."

But unlike Prolog's neverending sea of rules, Inform groups its rules into rulebooks. When a rulebook is invoked, only that rulebook's rules are considered. The only required part of a rule's header is the rulebook to which it belongs, though additional clauses are frequently attached using "during" (for scenes), "when/while" (for if-conditions), and a sole action description (such as "someone burning something") immediately following the rulebook name. This allows rulebooks to encapsulate smart, mutable behavior, similar to objects, but behavior here is paramount, not a data instantiation. It could be said that objects implement nouns, while rulebooks implement verbs.

In source code, a rulebook's rules are rarely found together or in any particular order. They can be scattered all over the source code, sprinkled throughout extensions, etc. An author is free to group his rules however he wishes, such as by the narrative scene or geographical area in which they're used, or by the rulebook to which they belong, or, yes, by the object(s) to which they apply.

There are a few cases where naming a rule is useful, but these are usually in addition to the header, not in spite of it. One is in debugging output: the RULES and RULES ALL testing commands list the names of rules as they execute, or are considering execution, respectively. Two is the Procedural rulebook's meta-rules (which are rarely needed). Three is when a hard-coded reference to the rule is required, such as initializing a rule variable, imperatively invoking a particular rule (rarely needed), or listing rules before, after, or instead-of other rules at compile time. Finally, it is always good extension-writing practice to name all rules merely so client code can reference, modify, or delete them, to say nothing of documentation.

My magic hand rule is listed instead of the can't take distant objects rule in the check taking rulebook.
The landlubbers can't walk right rule is listed first in the check going rules.
The block sleeping rule is not listed in any rulebook.
A procedural rule when the player is a ghost: ignore the can't walk through closed doors rule.
Inform 7 for Programmers by Ron Newcomb
Contents - Part 1 - Part 2 - Part 3 - Part 4 - Part 4b - Part 5