selkie.expr
— Predicate calculus
The module selkie.expr
contains the basic data structures for representing
predicate calculus expressions. It is separate from
selkie.interp
(which contains the semantic interpreter),
because the grammar module requires parse_expr()
.
Variables and constants
- class selkie.expr.Variable
Expressions are composed of variables (
x
,y
) and constants (forall
,if
,cat
). We implement both as strings, but to distinguish them, we provide a subclass ofstr
calledVariable
, and define anything that is not anExpr
or aVariable
to be a constant.>>> from selkie.expr import Variable >>> e1 = 'x' >>> e2 = Variable('x') >>> e1 'x' >>> e2 x
- fresh_variable()
One may create an “anonymous” variable by calling the function
fresh_variable()
:>>> from selkie.expr import fresh_variable >>> fresh_variable() _1
There is a global count of anonymous variables, and it is incremented each time an anonymous variable is created:
>>> fresh_variable() _2
When we parse a predicate calculus expression, we require some convention for
distinguishing between constants and variables. How do we
know that “x” should be converted to a Variable
instance, but
“Fido” should be left as a string? The Russell & Norvig text uses
the convention that variables are lowercase whereas names are
uppercase. Prolog and other automated reasoners use the opposite
convention: variables are uppercase and names are lowercase. The
convention I will adopt is that variables consist of a single letter,
such as “x
” or “P
,” whereas names contain at least
two (or none). To provide some
flexibility, a variable may optionally begin with underscore (which
does not count as a letter), and may optionally be suffixed with any
number of digits. Hence the following are recognized as variables:
_x
, x10
, _x10
; but not _Sk10
(which
contains two letters).
- is_variable_symbol()
The function
is_variable_symbol()
distinguishes constants from variables:>>> from selkie.expr import is_variable_symbol >>> is_variable_symbol('hi') False >>> is_variable_symbol('h1') True
Note that anonymous variables have names like
_1
that are not recognized as variables byis_variable_symbol()
. This is unproblematic: anonymous variables by definition never appear in string representations of expressions.
Predicate calculus expressions
Let us begin with a couple of examples of
predicate-calculus expressions.
We will represent them in Lisp format: Polish prefix
notation with obligatory grouping parentheses.
Operator symbols like “->” are replaced with names (”if
”):
(forall x (if (cat x) (animal x)))
(exists y (and (cat y) (not (orange y))))
((lambda x (or (cat x) (dog x))) Max)
- class selkie.expr.Expr
In keeping with our philosophy of simplicity, we implement expressions as tuples. An expression such as:
(chases Fido (the cat))
is implemented as:
('chases', 'Fido', ('the', 'cat'))
For convenience of display, however, we use the same trick that we used for
Category
: we define a classExpr
that is a specialization oftuple
.>>> from selkie.expr import Expr >>> e = Expr(['chases', 'Fido', Expr(['the', 'cat'])]) >>> e (chases Fido (the cat))
The words (variables and constants) in an expression usually print out without quotes, as in the example just shown. However, quotes are included if the word contains an embedded space.
>>> Expr(['every', 'bird dog']) (every 'bird dog')
- __str__()
The
__str__()
method produces a pretty-printed form. For example:>>> from selkie.expr import parse_expr >>> e = parse_expr('(if (forall x (loves x Fido)) (loves Fido Spot))') >>> print(e) (if (forall x (loves x Fido)) (loves Fido Spot))
- parse_expr(s)
The function
parse_expr()
that takes a string and converts it to an expression instance. Here is an example:>>> parse_expr('(and (dog x) (friendly x))') (and (dog x) (friendly x))
Expression parsing uses
selkie.io.tokenize()
for tokenization. This means that the delimiters[]{}
are treated as stand-alone tokens, and quoted strings are recognized as single tokens.>>> parse_expr('(x[y])') (x [ y ]) >>> parse_expr('(hi"there")') (hi there)
- scan_expr(toks)
The function
parse_expr()
dispatches toscan_expr()
, which scans a single expression from a token stream.>>> from selkie.io import tokenize >>> toks = tokenize('Fido (+ x y)') >>> from selkie.expr import scan_expr >>> scan_expr(toks) 'Fido' >>> scan_expr(toks) (+ x y)
- load_exprs(fn)
The function
load_exprs()
reads a file that contains expressions.>>> from selkie.data import ex >>> from selkie.expr import load_exprs >>> es = load_exprs(ex('cnf.expr')) >>> print(es[0]) (forall x (if (forall y (if (animal y) (loves x y))) (exists y (loves y x))))