An overview of the calculus module's role and abilities.
- §1. Prerequisites
- §2. What predicate calculus is
- §3. Notation
- §4. Formal description
- §6. Unary predicates
- §7. Binary predicates
- §8. Making propositions
§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:
- (a) It helps to have some experience of reading webs: see inweb for more.
- (b) The module is written in C, in fact ANSI C99, but this is disguised by the fact that it uses some extension syntaxes provided by the inweb literate programming tool, making it a dialect of C called InC. See inweb for full details, but essentially: it's C without predeclarations or header files, and where functions have names like Tags::add_by_name rather than add_by_name.
- (c) This module uses other modules drawn from the compiler, and also uses a module of utility functions called foundation. For more, see A Brief Guide to Foundation (in foundation).
§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 logic, different sets of rules for making deductions tend to be given such names, and we will use (a form of) one of the most popular simple systems, "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.
1 At time of writing, nearly 40 can be found here, though admittedly that includes a genus of spider and a Tintin character. ↩
2 Specifically, first order predicate calculus with equality, but with generalised quantifiers added, and disjunction removed. ↩
3 This is not altogether a coincidence since the pioneers of mathematical logic, and in particular Frege and Wittgenstein, began by thinking about natural language. ↩
§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.
'<< >>': << >> '<< is-a-kind (x) >>': << is-a-kind(x) >> '<< is-a-var (x) >>': << is-a-var(x) >> '<< is-a-const (x) >>': << is-a-const(x) >> '<< everywhere (x) >>': << everywhere(x) >> '<< nowhere (x) >>': << nowhere(x) >> '<< here (x) >>': << here(x) >> '<< called = magic passcode (x) >>': << called='magic passcode'(x) >> '<< kind = number (x) >>': << kind=number(x) >> 'new unary blue': ok '<< blue (x) >>': << blue(x) >> '<< (x == y) >>': << (x == y) >> 'new binary sees': ok '<< sees (x, y) >>': << sees(x, y) >> '<< NOT< ^ blue (x) ^ NOT> >>': << NOT< blue(x) NOT> >> '<< Forall x IN< kind = number (x) IN>: blue (x) >>': << ForAll x IN< kind=number(x) IN> : blue(x) >> '<< Exists x >>': << Exists x >> '<< DoesNotExist x IN< kind = number (x) IN>: blue (x) >>': << DoesNotExist x IN< kind=number(x) IN> : blue(x) >> '<< Forall x IN< kind = number (x) IN>: blue (x) >>': << ForAll x IN< kind=number(x) IN> : blue(x) >> '<< Card= 6 x IN< kind = number (x) IN>: blue (x) >>': << Card=6 x IN< kind=number(x) IN> : blue(x) >>
§4. Formal description. 1. A "term" is any of the following:
- ● A constant, corresponding to anything which can be evaluated to Inform — a number, a text, etc. — and which has a definite kind.
- ● One of 26 variables, which we print to the debugging log as x, y, z, a, b, c, ..., w.
- ● A function \(f\) applied to another term.4
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:
- ● A "unary predicate" \(U(t)\), where \(t\) is a term, which is either true or false depending on the evaluation of \(t\).
- ● A "binary predicate" \(B(t_1, t_2)\) depending on two terms.5
- ● A "quantifier" \(Q(v, n)\) applying to a variable \(v\), optionally with a parameter \(n\). See Determiners and Quantifiers (in linguistics) for the range of quantifiers available.
3. A "proposition" is a sequence of 0 or more of the following:
- ● A conjunction \(P_1\land P_2\), where \(P_1\) and \(P_2\) are propositions.
- ● A negation \(\lnot P\), where \(P\) is a proposition.
- ● A quantification \(Q v\in D: P\), where \(Q\) is a quantifier, optionally also with a numerical parameter, \(v\) is a variable, \(D\) is a set specifying the domain of \(v\), and \(P\) is a proposition.6
- ● An existential quantification \(\exists v: P\) without a domain.
4 In this module we use words such as "constant", "variable" and "function" in their predicate-calculus senses, not their Inform ones. For example, if we are to decide whether it is true that "a container in the location of Nicole contains the prize", we have to forget about the passage of time and think only about a single moment. In the resultant proposition, "the location of Nicole" and "the prize" lead to constant terms, "a container" leads to a variable term (since we do not know its identity) and there are no functions. ↩
5 We do not support higher arities of predicates as such, but they can be simulated. The universal relation in Inform is in effect a ternary predicate, but is achieved by combining two of its terms into an ordered pair. ↩
6 Some quantifiers also carry a numerical parameter, to express, e.g., "at least 7" — the parameter for that being 7. ↩
§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:
- ● \(\lnot P\) is implemented as NOT< P NOT>.
- ● \(Q v\in D: P\) is implemented as Q IN< D IN>.
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. We have a mixed bag of unary predicates, as follows.
- ● For each adjective defined in the linguistics module, there is a predicate A(t), true if the adjective currently applies to \(t\).
- ● For each kind \(K\), there is a predicate kind=K(t), which is true if \(t\) is of the kind \(K\).
- ● For any wording W, there is a predicate called=W(t), which is asserts that the current value of \(t\) is something with the name W.
- ● is-a-kind(t) asserts that \(t\) represents a kind, not a value.
- ● is-a-var(t) asserts that \(t\) represents a variable, not a value.
- ● is-a-const(t) asserts that \(t\) represents a constant, not a value.
- ● everywhere(t) asserts that \(t\) is an object found everywhere.
- ● nowhere(t) asserts that \(t\) is an object found nowhere.
- ● here(t) asserts that \(t\) is an object found in the current room.
As is apparent, the is-a-... predicates are a cheat: they exist purely to make it easier to write propositions which change the state of the world, rather than discuss that state. For example, Inform might create the kind "animal" by asserting Exists x : is-a-kind(x) ^ called=animal(x).
Otherwise, while these are all conceptually unary predicates, only the first is a PREDICATE_ATOM in our implementation: the others are KIND_ATOM, CALLED_ATOM,
§7. Binary predicates. By contrast all binary predicate atoms use PREDICATE_ATOM, and there is only one set of them — but with that said,
- (a) They can be organised into "families" with shared implementations — see Binary Predicate Families.
- (b) The equality predicate \(=\) is predefined by the calculus module, and its special meaning is used when simplifying propositions. See The Equality Relation. It is written with the special notation (x == y), though this is just syntactic sugar.
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
7 Except for equality, which is its own reversal. See BinaryPredicates::make_equality. ↩
§8. Making propositions. Propositions are built incrementally, like Lego, with a sequence of function calls.
1. Terms are made using the functions Calculus::Terms::new_constant, Calculus::Terms::new_variable and Calculus::Terms::new_function.
2. Unary predicate atoms are made using:
- ● Calculus::Atoms::unary_PREDICATE_from_aph_term, or
- ● Calculus::Atoms::KIND_new, or
- ● Calculus::Atoms::CALLED_new, or
- ● Calculus::Atoms::new for the six oddball unaries, supplying atom types ISAKIND_ATOM, ISAVAR_ATOM, ISACONST_ATOM, EVERYWHERE_ATOM, NOWHERE_ATOM and HERE_ATOM.
Binary predicate atoms are made using Calculus::Atoms::binary_PREDICATE_new.
3. Propositions are then built up from atoms or other propositions8 by calling:
- ● Calculus::Propositions::conjoin.
- ● Calculus::Propositions::negate.
- ● Calculus::Propositions::quantify.
8 But beware that propositions are passed by reference not value. Use Calculus::Propositions::copy before changing one, if you need to use it again. ↩
§9. There are two senses in which it's possible to make an impossible proposition:
- (1) You could make a mess of the punctuation markers improperly, or fail to give a domain set for a quantifier.
- (2) More subtly, you could concatenate two propositions in which the same variable is used with a different meaning in each.
The functions Calculus::Propositions::is_syntactically_valid and Calculus::Variables::is_well_formed test that (1) and (2) have not happened. It's because of (2) that it's important to use Calculus::Propositions::conjoin and not the simpler Calculus::Propositions::concatenate.
To illustrate:
'new unary even': ok '<< NOT> >> is syntactically valid': false - too many close groups '<< Exists x >> is syntactically valid': true '<< ForAll x >> is syntactically valid': false - ends without domain of final quantifier '<< ForAll x IN< kind = number (x) IN>: even (x) >> is syntactically valid': true '<< Forall x IN< kind = number (x) IN>: even (x) >> is syntactically valid': true 'set A to << Exists x >>': a set to << Exists x >> 'set B to << Exists x: even (x) >>': b set to << Exists x : even(x) >> 'A concatenate B': << Exists x : Exists x : even(x) >> 'A conjoin B': << Exists x : Exists y : even(y) >> 'A concatenate B is syntactically valid': true 'A conjoin B is syntactically valid': true 'A concatenate B is well-formed': false - x used outside its binding 'A conjoin B is well-formed': true