An overview of the calculus module's role and abilities.


§1. Prerequisites. The calculus module is a part of the Inform compiler toolset. It is presented as a literate program or "web". Before diving in:

§2. What predicate calculus is. The word "calculus" is often used to mean differentiating and integrating functions, but properly speaking that is "infinitesimal calculus", and there are many others.1 In particular, any set of rules for making deductions tends to be called a "calculus", and we will use a form of one of the most popular, "predicate calculus".2

Most attempts to codify the meaning of sentences in any systematic way involve predicate calculus, and most people generally seem to agree that linguistic concepts (like verbs, adjectives, and determiners) correspond uncannily well with logical ones (like binary predicates, unary predicates, and quantifiers).3 All the same, it is striking how good the fit is, considering that human language is so haphazard at first sight.

At any rate Inform goes along with this consensus, and converts the difficult passages in its source text into logical "propositions" — lines written in logical notation. This is useful partly as a tidy way to store complicated meanings inside the program, but also because these propositions can then be simplified by logical rules, without changing their meaning. Without such simplifications, Inform would generate much less efficient code.

§3. Notation. This module deals with propositions in predicate calculus, that is, with logical statements which are normally written in mathematical notation. To the end user of Inform, these are invisible: they exist only inside the compiler and are never typed in or printed out. But for the debugging log, for unit testing, and for the literate source, we need to do both of these.

A glimpse of the propositions generated by Inform can be had by running this test, whose output uses our notation:

Laboratory is a room. The box is a container.
Test sentence (internal) with a man can see the box in the Laboratory.
Test description (internal) with animals which are in lighted rooms.

But a much easier way to test the functions in this module is to use the calculus-test tool. As with kinds-test, this is a REPL: that is, a read-evaluate-print-loop tool, which reads in calculations, performs them, and prints the result.

'<< >>': << >>
'<< kind = number (x) >>': << kind=number(x) >>
'new unary even': ok
'<< even (12) >>': << even('12') >>
'<< even (x) >>': << even(x) >>
'<< (x == 7) >>': << (x == '7') >>
'new binary sees (none, sees-f1)': ok
'<< sees (x, y) >>': << sees(x, y) >>
'<< NOT< ^ even (x) ^ NOT> >>': << NOT< even(x) NOT> >>
'<< Forall x IN< kind = number (x) IN>: even (x) >>': << ForAll x IN< kind=number(x) IN> : even(x) >>
'<< Exists x >>': << Exists x >>
'<< DoesNotExist x IN< kind = number (x) IN>: even (x) >>': << DoesNotExist x IN< kind=number(x) IN> : even(x) >>
'<< Forall x IN< kind = number (x) IN>: even (x) >>': << ForAll x IN< kind=number(x) IN> : even(x) >>
'<< Card= 6 x IN< kind = number (x) IN>: even (x) >>': << Card=6 x IN< kind=number(x) IN> : even(x) >>

§4. Formal description. 1. A "term" is any of the following:

Note that if we have given values to the necessary variables, then any term can be evaluated to a value, and its kind determined. For example, if x is 7, then the terms 17, x and f(x) evaluate to 17, 7 and \(f(7)\) respectively.

2. An "atomic proposition" is any of the following:

3. A "proposition" is a sequence of 0 or more of the following:

§5. The implementation uses the term "atom" a little more loosely, to include four punctuation marks: NOT<, NOT>, IN<, IN>, which act like opening and closing parentheses. These are considered atoms purely for convenience when building more complicated constructions — they make no sense standing alone. Thus:

Note that the domain \(D\) of a quantifier is itself expressed as a proposition. Thus "for all numbers \(n\)" is implemented as ForAll n IN< kind=number(n) IN>.

In all other cases, adjacent atoms in a sequence are considered to be conjoined: i.e., X Y means \(X\land Y\), the proposition which is true if \(X\) and \(Y\) are both true. To emphasise this, the textual notation uses the ^ sign. For example, odd(n) ^ prime(n) is the notation for two consecutive atoms odd(n) and prime(n).

§6. Unary predicates. The calculus module aims to be agnostic about what unary predicates will exist. They are grouped into "families" — see Unary Predicate Families for details — which loosely group them by implementation. So, for example, Inform has a family of unary predicates in the form calling='whatever'(x) which assert that x represents something of a given name. But calculus is not concerned with the details. Only one family is built in:

New UPs can be constructed with UnaryPredicates::new.

§7. Binary predicates. Similarly, calculus allows the user to create as many families of binary predicates as are wanted. See Binary Predicate Families. For example, the "same property value as" relations all belong to a single family. This module builds in only one family:

Binary predicates are of central importance to us because they allow complex sentences to be written which talk about more than one thing at a time, with some connection between them. In excerpts of Inform source like "an animal inside something" or "a man who wears the top hat", the meanings of the two connecting pieces of text — "inside" and "who wears" — are binary predicates: the containment relation and the wearing relation. To avoid scaring the horses, binary predicates are called "relations" in all of the Inform documentation.

New BPs can be constructed with BinaryPredicates::make_pair. The term "pair" is used because every \(B\) has a "reversal" \(B^r\), such that \(B^r(s, t)\) is true if and only if \(B(t, s)\). \(B\) and \(B^r\) are created in pairs.7

§8. Making propositions. Propositions are built incrementally, like Lego, with a sequence of function calls.

1. Terms are made using the functions Terms::new_constant, Terms::new_variable and Terms::new_function.

2. Unary predicate atoms are made using Atoms::binary_PREDICATE_new. Binary predicate atoms are made using Atoms::binary_PREDICATE_new.

3. Propositions are then built up from atoms or other propositions8 by calling:

§9. There are two senses in which it's possible to make an impossible proposition:

The functions Propositions::is_syntactically_valid and Binding::is_well_formed test that (1) and (2) have not happened. It's because of (2) that it's important to use Propositions::conjoin and not the simpler Propositions::concatenate.

§10. Making propositions is a largely syntactic game, but in the end they will have meanings. Terms will have kinds, and relations will only apply to certain kinds. This means that some propositions which make syntactic sense will nevertheless be "bad": for example, \({\it contains}(7, x)\) is not just untrue but at some level meaningless — a number cannot contain things.

Throughout Inform, then, all propositions have to be type-checked before use, and this is done by TypecheckPropositions::type_check.

§11. Sentences. Our whole interest in propositions is to use them to provide a meaning for a sentence of natural language: we evaluate text like "Peter is in the car" into a proposition.9 This task is central to how Inform works, and occupies the whole of Sentence Conversions.

On the face of it, this is a simple matter of conjoining propositions for the subject (Peter) and the object (the car) together with a binary predicate (is-in). But most cases are not so simple, and if we did only that then we would often end up with a much longer proposition than necessary, which it would be inefficient to test or assert at run-time. So the sentence converter makes use of logical deductions to "simplify" its output, and these tactics form roughly 20 functions in the Simplifications section.