Inform 7 for Programmers/Part 1
Inform 7: In a Nutshell
Other than being a highly domain-specific language, the deepest differences between Inform 7 and most everything else in programmer-land are:
- Identifiers may have spaces. When objects, variables, functions, and actions are named appropriately, the result is extraordinarily readable code. Knuth would approve.
- The language is rulebook-based. Rulebooks are containers for rules. Rules give it a declarative flavor at the high level while remaining plainly imperative at the low level.
- The language is currently the most natural language-like programming language in the world.
The language Inform 7 ("I7") is:
- domain-specific: it compiles to a virtual machine not used outside of the I-F community, and rules are restricted to three parameters -- the highest arity an English verb may have
- event-based: events are called "actions" because the command line controls the protagonist's actions in the simulated gameworld
- object-based: single-inheritance and polymorphism are supported, but abstraction, modularity, and namespaces are poor to absent. I7 is a "white-box" development language by design
- cross-compiler: technically, Inform 7 source compiles to Inform 6 source, a weakly-typed multiple-inheritance traditional programming language, before compiling for the virtual machine. The dev team takes care to hide this complication from novice users.
- strongly typed: you'll swear you're in Pascal again, but inline Inform 6 code will circumvent
- statically allocated: extensions can be downloaded to circumvent this. The language is intended for non-experts.
- verbose: the true verbosity stems from circumventing strong-typing, and the lack of deeply nested mathematical-esque computations. But is insanely quick to type on a Dvorak layout. May also be pronounceable for voice-recognition input methods.
- has no unifying theory: some languages can say "everything is an object" or "everything is a list"; Inform doesn't have One True Construct. This makes it easier to learn at first because we can use and learn any feature while remaining ignorant of others. This organically-grown pattern satisfies Inform's target demographic of non-programmers. But at higher levels of programming, the language has a very ad-hoc feel to it.
- unique: Inform does boast some semantic conciseness unique to itself, which will hopefully be emulated in programming languages to come. We will mention these below as they occur.
Quick side notes:
- In this article as well as in Inform's official documentation, "the parser" always refers to the runtime parser, our player's simplistic VERB NOUN PREPOSITION NOUN parser. It does not mean that part of the Inform 7 compiler.
- "X is room." is the smallest program that will compile, not counting the automatically-added title and author listed on the first line. Every program must have at least one room.
- Hello world is: My apartment is a room. When play begins: say "Hello world."
- Comments belong in square brackets.
- With three exceptions, the articles "a", "an", and "the" are treated as whitespace.
Types, Variables, and Objects
We'll start by showing some variable declarations of every major type. For now, know that "is" and "are" are perfectly synonymous, and the articles "some", "a", "the", and "an" are simply stripped from the source.
- X is a number that varies. Y is a number variable.
- Deadline is a time that varies.
- An excuse is some text that varies.
- My favorite toy is a thing that varies.
- The current manager is a person that varies.
- The ocean currents are a direction that varies.
- The best spot is a room that varies.
- The light switch's boolean is a truth state that varies.
- The guru's answers are a table-name that varies.
- My secret plans are a rulebook that varies.
- What worked last time is a rule that varies.
- My regex target is some indexed text that varies.
- An abeyance is a stored action that varies.
Now how about some object instances, and a few relations between them. The following sample can be compiled because there is at least one room instance (assuming the very first line of the source has the work's title in double quotes; this line is automatically added).
- The apartment is a room. "Behold Bob's apartment. That smell is coming from the pile of dishes in the sink."
- The apartment complex's lobby is south of the apartment. "Rows of mailboxes are set into the wall. Box 114 is Bob's."
- Mr Bob Dobalena is a man in the apartment. "Bob is wearing an old Transformers t-shirt with some well-loved jeans."
- T-shirt, jeans, and a pair of shoes are wearable things. Bob is wearing the shoes, the T-shirt, and the jeans.
We have two Room instances ("apartment" and "apartment complex's lobby"), one Man instance, and three Thing instances ("T-shirt", "jeans", "pair of shoes") with their boolean "wearable" property set to true. The quoted text that's just floating by itself out there on its own is either put into the "description" property (for Rooms) or the "initial appearance" property (for other objects). There's two relations in there as well. Relations link two objects together, just like the standard is-a and has-a relations in OOP. Here we have the mapping-south relation (closely related to the Direction class), and the wearing relation thrice initialized between Bob and each article of clothing. (More on relations later.)
Note the following examples of conciseness: object names can be abbreviated ("Bob", "shoes"), one sentence can instantiate multiple objects (the three pieces of clothing "are ... things"), boolean properties have named values that act like adjectives (the value "wearable"; the property itself is anonymous), such a property can be set for multiple instances simultaneously (all three pieces are "wearable things"), and a relation can be set between multiple objects simultaneously (the "is wearing..." sentence).
Since some relations were initialized to true, we might as well show some other examples of supplying initial values.
- X is usually 2. Y is 5.
- Deadline is usually 4:30 pm.
- The excuse is usually "I didn't know."
- My favorite toy is usually the red Porsche.
- The light switch's boolean is usually true.
- The guru's answers are usually the Table Of Deep Answers.
The word "usually" isn't strictly needed for a variable, but a class property's initial value will be used for all of the class's instances unless the instance specifically says otherwise.
Class And Prejudice
Here's the built-in class hierarchy, a total of sixteen classes. (Also, where other languages say "class", Inform says "kind".) Inform purposely keeps its library lean.
- player's holdall
A quick rundown: Room is a discrete location, a place; Region is a container for Rooms. The class Container means an in-game prop, such as a backpack or hamster cage. A Supporter is a chair, table, mantel, or other horizontal surface Bob can place things on top of. The player's holdall is usually a singleton, as it's a container without load limits. A instance of Door can explicitly connect Rooms, and can be open & closed if not also locked & unlocked. Direction does NOT connect rooms -- relations do that -- but is used in code to reference the same. Backdrop always has the boolean property "scenery" set, and is used for things like the sun in the sky, the faint but ever-present sound of a nearby creek, and other non-portable objects that need to remain in the parser's scope while the protagonist travels across several Rooms. Device is something that can be switched on/off. Animal is treated as a kind of person, just as pets are.
The positioning of Animal is our first hint that we're stepping out of a scientific worldview, and into a humanistic one. Likewise, the purpose of the language as a whole is to produce works of art, not software tools.
Making a subclass is straightforward:
- An archway is a kind of door.
- An archway has a number called the horizontal clearance. It is usually 6.
- An archway is always open.
- An archway can be magic or mundane. An archway is usually not magic.
This subclasses archway from door. It gives it a new numeric property call the "horizontal clearance" with an initial value that can be overridden by a particular instance. Then it permanently sets the pre-existing open/closed property to open; attempts to later code a closed archway will result in a compiler error. Finally, it gives it a new anonymous boolean property with named values "magic" and "mundane" (as opposed to a property called "magic" with values "true" and "false"). It initializes this to "mundane". We could have just said "An archway can be magic" and it would still work, but naming the antonym usually improves readability.
One instance of Person is always provided for us: "yourself". "The player" is a Person variable initialized to "yourself" unless defined otherwise. (The player refers to his avatar's Person instance as ME.) One instance of Object (the root-level class) is always provided: "nothing". (Occasionally synonyms nowhere, nobody, no-one, and no one may be used, but it's usually a special-case syntax.)
- The player is Bob. [the "yourself" object still exists, though unused]
- [...]; change the player to Bob; [imperatively, at runtime]
The Coding Imperative
Here are examples of all the basic imperatives. Read the print statements within.
- let Z be 5; [local variable declaration; type inferred]
- now X is 5; [assignment]
- change Y to 4; [assignment, alternate syntax]
- if X > Y then say "The word 'then' flags a one-liner.";
- if X > Y, say "A comma is synonymous for 'then'.";
- otherwise say "'Otherwise' (or 'else') must be a one-liner if the 'if' was a one-liner. No punctuation follows it in this case.";
- if X > Y begin;
- say "'Begin' requires a semicolon of its own.";
- say "As does the matching 'otherwise' and 'end'.";
- end if;
- if X > Y:
- say "Python-esque style is OK as long as we don't mix the two styles in the same function.";
- say "Posting code to internet forums usually corrupts the tabs. But tabs are required elsewhere in the language, so, meh.";
- if X is greater than Y begin;
- say "This is an else-if chain. Also, notice that relational operators can be spelled out.";
- otherwise if X is Y;
- say "Only a semicolon is found for the trailing conditionals.";
- say "The final otherwise is the same as usual.";
- end if;
- if X is greater than Y:
- say "This is a else-if chain in Python style.";
- otherwise if X = Y:
- say "The punctuation here is more regular: always a colon, no matter what.";
- say "The = is rarely seen by itself, in practice; 'is' is easier to type.";
- unless X <= Y, say "'Unless' means 'if not'. Also, inequalities are written >= or <=, never => or =<.";
- while X > Y begin;
- say "While loops are almost never seen in practice. Infinite loops are too easy with them.";
- end while;
- while X > Y repeatedly say "Especially the one-liner version.";
- if X is:
- -- 1: say "A switch statement masquerades as an if statement. 'Unless' cannot be used.";
- -- 2: say "Python style only.";
- -- otherwise: say "There is no fall-through between cases, and no goto to restore it.";
- if my favorite toy is:
- -- the red Porsche:
- say "Numbers and objects both can be used in switches.";
- -- the jeans:
- say "Also note the further indentation of the cases, and their subsequent lines.";
- repeat with X running from 1 to 10 begin;
- say "'Repeat' is the usual loop construct. It has [X] forms. This enumerated one isn't used much.";
- end repeat;
- repeat with clothing running through every wearable thing begin;
- say "The 'description' type describes a set of objects.";
- if the clothing is the pair of shoes, say "Also, 'every', 'each', or 'all' aren't required here but may read better.";
- end repeat;
- repeat with target running through all limbs:
- if the target is the head, say "We can repeat through Named Values just as easily.";
- repeat with programmer running through the men who are in a lighted room (called the mainframe's area):
- say "[The programmer] in [the mainframe's area] says we can do some pretty complex stuff with 'descriptions'.";
- next; [ 'next' starts the loop over at the next iteration. C calls this 'continue' ]
- break; [ 'break' kills the loop immediately ]
- repeat through the table of designs:
- say "Tables are 2D arrays, We'll look at them in detail in their own section.";
Semicolons divide statements as usual, but the last semicolon should instead be a period. Alternately, the last statement should be followed by a blank line. This is how the end of a function is signified.
Inform calls the above imperatives, and other things like them, "phrases". Functions are also called phrases, because they are invoked similarly. But rules are different; they invoke themselves based on game events and other worldsim situations. Here's some quick examples just so we'll have somewhere to try out our imperative code.
- When play begins: say "Hello world!".
- Every turn: say "La-dee-da."
- Instead of taking yourself, say "You pull yourself up by your bootstraps and read on."
Remember to instantiate a room.
Adjectives, and the Description type that invokes them, are Inform's big win over the COBOL and HyperTalk families of natural-esque programming languages. Typically used for objects, a description uses combinations of adjectives with a class to declaratively create a set of instantiations. This is responsible for a great deal of Inform's conciseness over traditional programming languages. First we'll look at how to define the adjectives.
There are a few ways to define a boolean adjective. The first creates an anonymous boolean property with named values.
- A thing can be spiffy.
- A person can be grumpy or happy.
- now the iPod is not spiffy;
- if Mary is happy, change Bob to grumpy;
Inform of course understands "not spiffy", but it also understands "not grumpy" as synonymous with "happy" and vice-versa. Also, since Person is subclassed from Thing, a person can also be spiffy. Frequently in OOPLs, new properties and methods cannot be added to a pre-existing class because doing so would break pre-existing code that uses it; subclassing is required to add such embellishments. But since Inform isn't designed for re-usable code or even team-built works, Inform allows the direct modification of classes, and those changes are propagated down the subclasses regardless whether they are built-in or not. The allowed modifications are additive only, however. (But see the Hacking section at the end.)
The second way to define an adjective works like adding a boolean-returning method to a class. It also has a one-liner version using "if" instead of a second colon:
- Definition: a person is boring: [...]; [...]; decide no.
- Definition: a person is unlikable if it is boring or it is grumpy.
- Definition: a person (called the academic) is laconic rather than chatty if the academic is [...].
The word "it" is used to refer to the object on which the method is called, where traditional languages use "self" or "this" or some such. This can be customized with the "called" parenthetical. An antonym may be defined with the "rather than" phrase. The "Definition:" adjective definition isn't restricted to objects: almost any value that can be put into a variable can use the one-liner version (see the "is an X that varies" samples up top). Finally, this is one of the places where the identifier -- the adjective -- must be a single word. Hyphenated names are common here.
The third way, using a generic "to decide whether" boolean function (see below) is the least flexible because adjectives defined this way cannot combine with other adjectives. It works like a traditional boolean function. Not all things in our code will be implemented as objects: table rows, scenes, rules, rulebooks, named values, etc. Some calculated values really don't "belong" to anything or anywhere. But we frequently want these non-objects to emulate the fluid description style of objects, and the generic boolean "to decide whether" function is a primary way to do this.
The adjectives are checked the same way regardless of the storage versus calculation implementations.
- repeat with associate running through every chatty not grumpy spiffy person begin;
- say "Hi [associate].";
- end repeat;
So, changing an adjective from a variable implementation to a calculated implementation (or vice-versa) only requires adding or removing any lines that explicitly set the adjective's value; client code that merely checks the value needn't change.
|Inform 7 for Programmers by Ron Newcomb|
|Contents - Part 1 - Part 2 - Part 3 - Part 4 - Part 4b - Part 5|