
The emptiness check: A “ H ðñ A Ă H. The dis-
joint check: A K B ðñ A X B Ă H. Type equivalence
A “ B ðñ A Ă B and B Ă A.
Common Lisp has a flexible type calculus making type
specifiers human readable and also related computation pos-
sible. Even with certain limitations, s-expressions are an
intuitive data structure for programmatic manipulation of
type specifiers in analyzing and reasoning about types.
If T1 and T2 are Common Lisp type specifiers, the type
specifier (and T1 T2) designates the intersection of the types.
Likewise (and T1 (not T2)) is the type difference. The empty
type and the universal type are designated by nil and t re-
spectively. The subtypep function serves as the subtype
predicate. Consequently (subtypep ’(and T1 T2) nil) com-
putes whether T1 and T2 are disjoint.
There is an important caveat however. The subtypep
function is not always able to determine whether the named
types have a subtype relationship [5]. In such a case, subtypep
returns nil as its second value. This situation occurs most
notably in the cases involving the satisfies type specifier.
For example, to determine whether the (satisfies F) type
is empty, it would be necessary to solve the halting problem,
finding values for which the function F returns true.
As a simple example of how the Common Lisp program-
mer might manipulate s-expression based type specifiers,
consider the following problem. In SBCL 1.3.0, the expres-
sion (subtypep ’(member :x :y) ’keyword) returns nil,nil,
rather than t,t. Although this is compliant behavior, the
result is unsatisfying, because clearly both :x and :y are
elements of the keyword type. By manipulating the type
specifier s-expressions, the user can implement a smarter
version of subtypep to better handle this particular case.
Regrettably, the user cannot force the system to use this
smarter version internally.
( de fun s m a r t e r - s ubtypep (t1 t2 )
( m u ltiple-value-bind ( T1 <= T2 OK ) ( subty p ep t1 t2 )
( cond
( OK
( val ues T1 <= T2 t ))
;; ( eql obj ) or ( me mber obj1 ... )
(( t ype p t1 ’( c ons (mem ber eql me mber )))
( val ues ( ever y #’( lamb da ( obj )
( ty pep obj t2 ))
( cdr t1 ))
t ))
(t
( val ues nil nil )))))
As mentioned above, programs manipulating s-expression
based type specifiers can easily compose type intersections,
unions, and relative complements as part of reasoning algo-
rithms. Consequently, the resulting programmatically com-
puted type specifiers may become deeply nested, resulting
in type specifiers which may be confusing in terms of hu-
man readability and debuggability. The following program-
matically generated type specifier is perfectly reasonable for
programmatic use, but confusing if it appears in an error
message, or if the developer encounters it while debugging.
( or
( or ( and (and num b er ( not bi gnum ))
( not (or fix num ( or bit ( eql -1 ) ))))
( and (and ( and numb er ( not bi gnum ))
( not (or fix num ( or bit ( eql -1 )))))
( not (or fix num ( or bit ( eql -1 ) )))))
( and (and ( and numb er ( not bi gnum ))
( not (or fix num ( or bit ( eql -1 ) ))))
( not (or fix num ( or bit ( eql -1 ) )))))
This somewhat obfuscated type specifier is semantically
equivalent to the more humanly readable form (and number
(not bignum) (not fixnum)). Moreover, it is possible to write
a Common Lisp function to simplify many complex type
specifiers to simpler form.
There is a second reason apart from human readability
which motivates reduction of type specifiers to canonical
form. The problem arises when we wish to programmati-
cally determine whether two s-expressions specify the same
type, or in particular when a given type specifier specifies
the nil type. Sometimes this question can be answered by
calls to subtypep as in (and (subtypep T1 T2) (subtypep
T2 T1)). However, as mentioned earlier, subtypep is al-
lowed to return nil,nil in some situations, rendering this
approach futile in many cases. If, on the other hand, two
type specifiers can be reduced to the same canonical form,
we can conclude that the specified types are equal.
We have implemented such a function, reduce-lisp-type.
It does a good job of reducing the given type specifier toward
a canonical form, by repeatedly recursively descending the
expression, re-writing sub-expressions, incrementally mov-
ing the expression toward a fixed point. We choose to con-
vert the expression to a disjunctive normal form, e.g., (or
(and (not a) b) (and a b (not c))). The reduction proce-
dure follows the models presented by Sussman and Abel-
son [1, p. 108] and Norvig [14, ch. 8].
4. BINARY DECISION DIAGRAMS
A challenge using s-expressions for programmatic repre-
sentation of type specifiers is the need to after-the-fact re-
duce complex type specifiers to a canonical form. This re-
duction can be computationally intense, and difficult to im-
plement correctly. We present here a data structure called
the Binary Decision Diagram (BDD) [6, 2], which obviates
much of the need to reduce to canonical form because it
maintains a canonical form by design. Before looking at
how the BDD can be used to represent Common Lisp type
specifiers, we first look at how BDDs are used tradition-
ally to represent Boolean equations. Thereafter, we explain
how this traditional treatment can be enhanced to represent
Common Lisp types.
4.1 Representing Boolean expressions
Andersen [3] summarized many of the algorithms for ef-
ficiently manipulating BDDs. Not least important in An-
dersen’s discussion is how to use a hash table and dedicated
constructor function to eliminate redundancy within a single
BDD and within an interrelated set of BDDs. The result of
Andersen’s approach is that if you attempt to construct two
BDDs to represent two semantically equivalent but syntac-
tically different Boolean expressions, then the two resulting
BDDs are pointers to the same object.
A1
A2
1 A3
1 0
A3
0 1
Figure 6: BDD for pA
1
^A
2
q_pA
1
^ A
2
^A
3
q_p A
1
^ A
3
q
Figure 6 shows an example BDD illustrating a function of
three Boolean variables: A
1
, A
2
, and A
3
. To reconstruct the
DNF (disjunctive normal form), collect the paths from the