Types of Types
We know defining a class that implements a list of numbers is tedious, but not as tedious as doing it again for strings, objects, and any other type the language supports. Most languages nowadays provide for this problem, such as the templates of C++. Inform too offers generic types. “Arithmetic value” allows numbers and units. “Pointer value” includes indexed text, stored actions, and other dynamically-allocated, variable-sized chunks of memory. “Word value” covers all non-pointer values. “Sayable value” is any type allowed in a print statement, which is almost anything. “Enumerated value” is enums, which includes scenes. And finally, “value” is all of them: just about anything that can be passed as a parameter. The Kinds Index in the IDE shows what types belong under which umbrellas. We can use them to define a phrase like this.
To place angle brackets around (foobar - a sayable value): say "<<[foobar]>>".
To print the elements of (stuff - a list of sayable values): say "The list contains [stuff]."
Kind variables tie two or more parameters to the same type. This is particularly useful when one parameter is an aggregate type, and the other is the type being put into, brought out of, or compared to an element of, that aggregate. Kind variables are always a single capitalized letter, and traditionally use K and L.
To decide which K is the initial contents of (stuff - a list of arithmetic values of kind K): ...
To we will ask if (col - a K valued table column) is (data - a word value of kind K): ....
The words “… value of kind …” must precede exactly one of the Ks. That parameter will be the one that declares what K holds, so the other parameter’s input will be expected to match. For example, the following line means the second parameter decides what K is, so the compiler will search for a table column of that type:
To we will ask if (col - a K valued table column) is (data - value of kind K): ....
While this means the first parameter’s type sets K, and the second will be interpreted as the type in question:
To we will ask if (col - a value of kind K valued table column) is (data - K): ....
It makes a difference. A very few types are implicitly casted between each other, such as text and indexed text, and whether K is text or indexed text will likely matter in the body of the function. Other times, the compiler may have a choice of constructs with the same name but different types. Or, the same words of source text may have radically different interpretations depending on what type the compiler expects it to yield. And one final note: the return value cannot set the kind variable. It may use one, as the above example with initial contents, but cannot declare it.
The parameter “name of kind of value” is an interesting case because, rather than accepting a particular instantiation of a restricting type, it asks for the source code’s name of a type, such as “object” or “room” or “tattoo”. K is set to the type, and then K is used in the body of the function in the same places and same ways as the word itself would have appeared. The feature is also very useful in typecasting, via Inform 6.
To rattle off all the (name of kind of value K):
repeat with x running through K:
say "[x], "
To decide which K is the (mystery - a value) as a (name of kind of value K):
(- {mystery} -).
We can then write:
rattle off all the scenes;
rattle off all the tattoos;
rattle off all the rooms;
let whatsit be foobar as a tattoo;
let whosit be foobar as a person;
let howsit be foobar as a rule;
let whensit be foobar as a scene;
And so on. Most OO languages tend to treat everything as an Object – even simple functions. In Inform, many constructs are not Objects, but the type system allows us to use the same functions on everything, just the same.
Note that parameter types “condition” and “action” are still a special case. They can only be used in a phrase when the body is written in Inform 6, because they expand to the condition of an if-statement, or a particular invocation of TryAction(), respectively. The pragma {-backspace} can be used to delete, character by character, their I6 forms.
To decide if maybe (cond - a condition): (- ({cond} && StatCheck()) -).
To (P - person) should try (act - an action): (- Could{act}{-backspace}{-backspace}, {P}); -).
To-phrases and relations can be passed as parameters, but still strongly-typed. Examples of the type of a phrase would be:
a phrase (length, length) -> nothing
a phrase nothing -> number
a phrase (number, number) -> number
And for relations:
a one-to-various relation of people to cars
a symmetric one-to-one relation of people ["to people" is assumed]
an equivalence relation of people
One thing Inform cannot do is an indefinite number of parameters, such as the argc/argv combination in the C programming language. And because Inform is strongly typed, there must be a slew of phrases that apply a passed-in phrase to 1, 2, or 3 inputs, and returning a value or not. For phrases that return a value, use one of “(phrase) applied”, “(phrase) applied to (value)”, “(phrase) applied to (value1) and (value2)”, up to three parameters, just like rules. For phrases that do not return a value, “apply (phrase)”, “apply (phrase) to (value1)”, and so on.
In lieu of indefinite parameter counts, Inform can pass around lists of whatever, and has the higher-order phrases map (called “(phrase) applied to (list)”), filter (“filter to (description) of (list)”), and reduce (“(phrase) reduction of (list)”), which apply a passed-in phrase to a list of values and return the new list. However, this only emulates the syntax of functional programming. Without lazy evaluation or anonymous functions, this style of programming is inefficient in runtime and in writing, and cannot at all deal with lists of infinite size in the common generator-consumer pattern.