implementation translator.
It should look something like this:
(Defun F (relname keywords &rest arguments) ...)
Relname and keywords are the first argument and the rest of the argument list
passed to DefRelation. Arguments
is the rest of the implementation parameter (which of course is also contained
in the keywords list). All arguments to defrelation other than the
implementation are used only by F. It should generate warnings
if it decides to ignore them. Normally it will include them in its result.
As an example,
(DefRelation R2 :representation (D2 1 4) :adder add-R2)
would be expanded by doing
(funcall 'D2 'R2 '(:representation (D2 1 4) :adder add-R2)
1 4)
The result might be something like
(:arity 2 :representation MyRep
:adder add-R2 :tester test-R2)
in which case the original defrelation would be roughly equivalent to
(DefRelation R2 :arity 2 :representation MyRep
:adder add-R2 :tester test-R2)
The only difference is that the original implementation, in this case the
list (D2 1 4) would be related to the relation R2 by the following relation:
Relation (parameter-list relation list)
This is used for describing the relation and may also be used elsewhere,
e.g., test-R2 might actually read it.
It is conventional to use the same name for parameterized
implementations and the functions that translate them, i.e., the SUM
derivation is translated by the Sum function, so that a derivation of
the form (SUM ...) translates into a derivation of SUM.
If none of implementataion parameters is supplied, the default
is representation base.
Documentation is a documentation string.
Arity is the arity of the relation. If a definition is supplied the
length of its argument list will be used as the arity. An error will
be signalled if this disagrees with a supplied arity.
Some implementation translators supply an arity. Otherwise, if
equivs or types are specified, arity defaults to the maximum of their
lengths. Otherwise the arity must be specified.
Equivs is a list of comparisons for the slots. (See the section on
equivalence.) Each element
is either the name of an equivalence relation, :any for an anycomparison
slot, or :default. If the list is shorter than the arity, any missing
slots will be treated like :default. If a definition is supplied, the
comparisons will be computed from it, and any non-defaulted comparison
that disagrees will cause an error. Otherwise, defaults are derived
from the type constraint of the slots, as specified by the Types parameter.
Comparisons are derived from type constraints as follows:
If the type constraint is a description, it is analyzed to find the
comparison of its argument. (This could be :any.) Otherwise,
if the type is NOT in the AnyComparison relation, then its comparison
is used (as it must be for the typeconstraint to be well formed).
Otherwise the defaults are examined.
Relation (default-equivalence type equivalence-relation)
If all of the most specific supertypes of a type that have defaults
happen to agree on a single default, that one is used.
Otherwise a continuable error is signaled from which the user can
name a comparison.
Definition is a description of which tuples are in the relation.
Types is a list of type constraints, either type names or unary
descriptions. If its length is less than arity, any remaining slots
will be unconstrained (type entity).
Argnames allows you to specify a list of names (symbols) for the arguments
of the relation. If this argument is not supplied, the argument names
can also be supplied using the syntax described in
esoteric syntax as part of the definition if there
is one, or otherwise as part of the the types argument.
For instance, instead of
(Defrelation supervisor :types (person person) :argnames (underling boss))
one might write
(Defrelation supervisor :types (person#underling person#boss) ...)
to indicate which argument was which. For cases where the type argument
contains a description, an argument name can be specified as in
(Defrelation supervisor
:types (((person#underling) s.t. ...) ...) ...)
The argument names for a relation are stored in the
relationargnames relation.
Type-enforcements is a corresponding list of enforcements for the
type constraints. If its length is less than the length of Types,
any remaining enforcements will be defaulted. The default, which
may also be specified by the symbol :default, is :none for derived
relations and relations with definitions, and :incremental for others.
In addition to an enforcement, this argument may supply a reaction in
the same way as reactions are supplied to DefTypeConstraints.
A given enforcement may be either an enforcement name, e.g., :none,
a reaction, i.e., :remove, :norepair or (:repair function) where
function is the reaction, or a 2 element list containing
an enforcement name and a reaction in either order. Reactions default
to :remove. Unlike DefTypeConstraints, neither enforcements nor
reactions are sticky.
Size is a list alternating between input/output patterns and numbers,
where each pair is a tuple to be added to the relsize relation with the
new relation cons'd onto the front, e.g.,
:size ((input input output) 10 (output output output) 100)
Count is interpreted as a set of argument lists for restrict-cardinality:
If you take a list of restrict-cardinality forms, remove the first two
elements of each ('restrict-cardinality and the relation name), and then
append all the lists
together, you'll have an acceptable :count argument.
None of these should use the :delete-any-other-countspecs argument. Instead
:keep-other-countspecs can be specified in DefRelation. If it is
T then all count constraints will be retained. Otherwise it should be a list
of patterns of count constraints to keep. The default is NIL. Any
count constraints other than
these and the ones specified by the :count argument will be deleted.
Updater, Tester and Testeffort are the relupdater, reltester and testeffort
of the relation.
Generator specifies a set of generators. It may be either a single
generator or a list of generators. A single generator is either a
symbol, which is assumed to name a function, a list starting with Lambda,
which is assumed to be a function, or a list starting with
SimpleGenerator or SimpleMultipleGenerator, which is just like the
corresponding form that could be evaluated except that the arguments
need not be quoted and the relation name is omitted from the pattern,
e.g., (simplegenerator (x output y) (ignore-errors (- y x))).
Trigger, if specified, should be a list of trigger specifications, as in
(defrelation derived-rel ...
:trigger ((r1 + f1 +) (r1 - f2 -)) ...)
which means that f1 computes additions to derived-rel (that's the second +)
from additions of r1 (the first +) and f2 computes deletions from
derived-rel from deletions of r1.
Notice in this syntax, + and - are used to represent new truth values
of true and false. The symbol +- may be used to mean both. This is
equivalent to two trigger specifications, one containing + and the other
-. (You can use two +-'s in the same specification, in which case it's
equivalent to four specifications.) Similarly, the relation may be a
list of relation names, in which case it stands for one copy of the
specification for each name.
If the functions are specified by symbols
(function names), the definitions will have to come after the DefRelation
since they will do adds and deletes of this relation. Finally, if any of
the "function" specifications is NIL, this is taken to mean that there is
a dependency that is not specified. This currently has the effect of
declaring the relation nontriggerable.
Inline means to declare the relation to be inline. It defaults
to T iff no representation is provided, a definition is provided and the
definition "expands to" a non-compound wff.
Nonatomic means that the relation is nonatomic as described above. In
this case it becomes relevant that additions and deletions to the
relation may be idempotent, i.e., the code works whether the fact was
already true or not. This can be specified by the idempotent argument,
which may be NIL (the default), (:add), (:delete), or (:add :delete).
If :add is in the list then it is assumed safe to call the adder or
updater to add a tuple without first checking that the tuple to be added
is false. Similarly, if :delete is in the list it is assumed safe to
call the deleter or updater to delete a tuple without first checking that
it was true. For fanatical optimizers, the allow-interrupts argument
specifies that updates may be done without the protection of a critical
section.
Cache-Generators means to compute a set of generators for the relation.
This is likely to save time later when the relation is used.
If the value is T then all possible generators will be precomputed.
NIL means to compute none. Otherwise the value should be a list of
patterns for which generators should be cached. Each pattern is a
list whose length is the arity of the relation being declared and whose
elements are symbols named INPUT or OUTPUT. The pattern means to cache
a generator which accepts bindings for the INPUT positions and generates
bindings for the OUTPUT positions.
The default value is T for relations of arity 3 or less and NIL for others.
Alsofns is a list of functions to be called in the same transition where
the implementation is stored. This is typically used for storing other
related attributes of the relation.
These functions are passed the relation (not its name) as their only argument.
Note --
The intent of defrelation is that it should be possible to use it to "redefine"
a relation without an intermediate state that would cause such damage as
removing all of the rules and relations that depend on it.
(This would happen if you removed the relation and then defined it again.)
Alsofns tends to be used for adding data about the relation, sometimes in
the form of other relations that describe the newly defined relation,
but not always. When the relation is redefined, often this additional
data ought to be removed, or at least modified, and this is currently not
supported by the alsofns mechanism.
I now think that this should be replaced by a declarative description of
what data is to be added by the current defrelation. This should be stored
with the relation, and then defrelation, if the relation already existed,
should arrange to compare the new additional data to the old version, and
make appropriate changes atomically.
For instance, the replacement for alsofns might report that we are to
add the relationship (tclosure R1 R2) as the result of this definition of R2
(meaning that R2 is the transitive closure of R1).
In that case, if the relation R2 already existed, we should find the added data
for the old definition and compare it to the data to be added for the new
definition in order to compute a set of changes to make, e.g., if R2 was not
previously a transitive closure at all, we would remove its old data and add
the tclosure tuple.
Current uses of alsofns:
- unionclass and intersectionclass add subtype relationships
(which are not undone on redefinition)
- basetype adds subtype relationships (and matchnode?)
- lisp-predicate-equivalence-relation (adds canonicalizer,equivalence-relation)
- tclosure (asserts tclosure)
- partial-index (can remove old index data)
- slot(-single), stored-in-place(-single) and all variants (asserts stored-where)
(to be fixed)
When a DefRelation form is evaluated for an already existing relation, the
assumption is that its definition is being changed, and the result ought
to be something like the result of deleting the relation and then redefining
it. However deleting the relation would remove certain data that might have
been added after the previous definition, and it's not clear that this is
the intent. Our current solution works as follows:
If the representation, derivation, or arity of the relation changes, all
adder, deleter, tester, generator, trigger, size, and count data is removed
from the old relation. These are presumed to be justified by the
arity and representation or derivation of the relation.
Regardless of whether the representation, derivation or arity changes,
all values provided in these parameters are then added, and any values
provided in the previous definition (using DefRelation) but not provided in
this definition are removed.
Defattribute - the common special case
Most relations turn out to be binary. For this common case, the following
macro offers more succinct syntax than defrelation:
Macro (defattribute relname domain-name range-name ...)
The arguments all translate directly into arguments of defrelation as
follows:
Relname is the relation name - the first argument of defrelation,
Domain-name is the first element of defrelation's :types.
Range-name is the second element of defrelation's :types.
The rest are keyword arguments:
:Domain-equivalence and range-equivalence are the first and second
elements of defrelation's :equivs. :Domain-type-enforcement and
:range-type-enforcement are the corresponding elements of
defrelation's :type-enforcements.
:Countspec is the count keyword (such as :any) for the pattern
(domain-name output).
:Inverse-countspec is the one for (output range-name).
:Replacing and :inverse-replacing specify that the too-many repairs
for these should replace old values. :Default and :inverse-default
are the corresponding default value functions for too-few repairs.
:Count-enforcement and inverse-count-enforcement are the corresponding
enforcements.
:Size is the number of tuples for the pattern (domain-name output),
:inverse-size is the number for the pattern (output range-name), and
:total-size is the number for the pattern (output output).
:Inverse-name is the name for another relation to be declared as the
inverse of this one, i.e., if this relation relates x to y then the
inverse relates y to x. (This does not correspond to any argument of
defrelation.)
:Definition, :derivation, and :computation are as in defrelation.
Creating new representations, derivations, computations
New representations and derivations can be declared via these two macros:
Macro (defrepresentation name
&key updater tester generators testeffort checkers idempotent)
Macro (defderivation name
&key updater tester generators testeffort)
Macro (defcomputation name
&key tester generators testeffort)
Database representation of domain model
While we have provided a lot of convenient macros for declaring relations
and rules, these are also objects in the database. Specifically, all
rules and relations are DBObjects with various attributes.
For relations some of these have been mentioned before. Many others are
described later (under "Finer Control over Implementation").
Formally, we do not regard the declaration of a relation as making
infinitely many facts true and/or false (even though the declared relation
might have infinitely many tuples). Rather, we regard it as adding
a small number of facts about an existing (but previously "unused") object.
The relations affected by a declaration include the following.
Relations
Relation (relationname relation name)
means that name is the name of the relation object relation.
Relation (relationarity relation arity)
means that arity is the arity of the relation object relation.
An object with a RelationArity is, by definition, a relation object.
Relation (relationimplementation relation implementation)
means that implementation is the representation, computation, or
derivation (a symbol) of the relation object relation.
Type (relation relation)
is true of all primitive relations, i.e., (listof relation) will
give a list of relations (not their names).
Relation (wffdefn relation description)
associates relations with definitions.
Relation (relationargnames relation argumentlist)
This records the argument names supplied with the definition of the relation.
The list can be nil, in a case like
(Defrelation supervisor :arity 2)
or a list of nil's, in a case like
(Defrelation supervisor :types (person person))
or a list of more useful symbols, e.g., (underling boss), in a case like
(Defrelation supervisor :types (person#underling person#boss))
or in a case like
(Defrelation supervisor :definition ((person#underling person#boss) s.t. ...))
or in a case like
(Defrelation supervisor :arity 2 :argnames (underling boss))
Relation (doc symbol symbol string)
This is the relational counterpart of the commonlisp Documentation
function. In particular, relation names may have documentation
associated with the symbol Relation and rule names with the symbol
Rule. These are printed by DescribeRel and DescribeRule and may be
included in the forms that are normally used to declare relations and
rules.
Relation (basetype type)
means that x is a basetype. The adder for a basetype classifies
an object as the basetype. (However there is a rule that may remove
this classification if the object is also classified as a subtype.)
The deleter removes such classifications.
Relation (classification x type)
means that x is of type t1. There is no
requirement that for every type t1 s.t. (t1 x), x be classified as a t1
(or any subtype of t1). Of course, every member of a basetype is classified
as a subtype of that basetype. Normally it's more useful to ask about
(t1 x) than (Classification x t1).
changes arbitrarily
As an example, suppose
you were given the functions
HardCopy-Device-P(string) ; is string the name of a printer
Add-HardCopy-Device(string) ; make string the name of a printer
You might declare a HardCopy-Device relation as follows:
(DefRelation HardCopy-Device :arity 1 :representation individual
:tester
(lambda (&rest ignore) (declare (ignore ignore))
'(lambda (rel x) (declare (ignore rel))
(HardCopy-Device-P x)))
:adder
(lambda (&rest ignore) (declare (ignore ignore))
'(lambda (rel x) (declare (ignore rel))
(Add-HardCopy-Device x)))
:nontriggerable t)
The nontriggerable means that Add-HardCopy-Device might be called directly, as
opposed to being done as a result of (++ HardCopy-Device ...). If you
wish to use HardCopy-Device in rules you must "promise" that this will not
happen, i.e., remove the :nontriggerable t. In this example, there is no way
to remove a HardCopy-Device or to generate all HardCopy-Devices. If these
capabilities existed they could be included in the DefRelation form.
}
Representations, derivations and computations are all represented by the
single relation:
Relation (implementation symbol)
Those that are derived or computed are represented by the subtype:
Relation (derived implementation)
Many attributes are shared among these implementation objects and relations.
In general, a relation "inherits" these attributes from its implementation.
The following are all parameters of defrelation:
Relation (testeffort rel-or-imp function)
Relation (relsize relation pattern size)
Relation (relupdater rel-or-imp function)
Relation (reladder rel-or-imp macro)
Relation (reldeleter rel-or-imp macro)
Relation (reltester rel-or-imp macro)
Relation (relgenerator rel-or-imp macro)
Triggers are not inherited from implementations. They are represented as
follows:
Relation (trigger rel1 tv1 function rel2 tv2)
This implies that rel2 is derived from rel1, and that whenever a tuple
of rel1 acquires the truth value tv1 (either T or NIL), the function
will compute tuples of rel2 which therefore acquire truth value tv2.
Similarly the following are only defined for relations.
Relation (possibletoadd rel t-or-nil)
Relation (possibletodelete rel t-or-nil)
Relation (inlinerel relation)
Rules
Type (rule x)
is true if the object is a rule. More specifically, it is the union
of the following subtypes of rule:
Type (consistencyrule x)
Type (consistencychecker x)
Type (automationrule x)
Type (automationgenerator x)
Type (maintainrule x)
(See below for a description of maintainrules.)
The components of consistency rules are accessible via:
Relation (ruletrigger rule trigger)
Relation (rulename rule name)
Relation (rulecode rule code)
Relation (constraint-enforcement-level rule enforcement)
Relation (inconsistency-reporter consistency-rule function)
Other rules also have names, triggers and code.
Particular types of consistency rules are also accessible from the
database:
The following relations provide access to count constraints:
Relation (multiple-for relname pattern)
means that there is a rule with a trigger that looks like the kind that
Restrict-Cardinality would produce if passed the relation name and
the pattern with a countspec of :multiple or :unique.
Relation (optional-for relname pattern)
is the corresponding thing for countspecs of :optional or :unique.
Relation (none-for relname pattern)
is the corresponding thing for countspec :none.
Relation (unique-for relname pattern)
means bot optional-for and multiple-for.
Relation (any-for relname pattern)
means none of the above.
SubRel is actually defined as the transitive closure of:
Relation (isubrel subrel superrel)
This means that subrel
is an "immediate" subrelation of superrel. ISubrel is actually
a maintained relation that reflects the presence of a constraint which
requires every tuple of the subrelation to be a tuple of the superrelation.
Such constraints are recognized by the form of their triggers.
Relation (subreltrigger subrel superrel arity trigger)
means that a consistency rule with the given trigger would require
that subrel be a subrelation of superrel (and that it is recognized
as such by ISubRel).
Relation (typeconstraint relation position type)
means that there is some rule whose trigger looks like the kind created by
DefTypeConstraints which requires the position'th element of any tuple
in relation to be of type type. Positions start at 0.
It turns out that the form of a type constraint for a unary relation is
identical to the form of a subtype constraint. In fact, the rule that
is created either way will be recognized as an instance of both. The
only difference is in the default enforcement and the action to be taken
in order to maintain consistency.
The relevant relation for disjointness constraints is:
Relation (disjointtypes type1 type2)
This is true if the two types have been declared disjoint or are subtypes of
two such types.
Relation (idisjointtypes type1 type2)
This relates pairs of types that have a rule specifically declaring them
disjoint.
Rules that Maintain Relations
Efficiency considerations sometimes dictate that a relation that could be
defined ought to be cached. For example, the definition is expensive to
access, and the relation is accessed much more often than it is updated.
In AP5, the way to get the right thing to happen is to create a rule
that causes the (stored) relation to track the definition. This could be
(and in fact used to be) done by consistency rules. Unfortunately, such
an implementation does not quite achieve the same effect as a defined
relation. For example, an update that appears to cause a change in a
defined relation might trigger a consistency rule which "undoes" that
change. If the defined relation were replaced by a stored relation that
was maintained by another consistency rule, the attempt to "undo" the
change would cause an abort (whereas the defined relation is free to
change back).
Instead of using consistency rules, AP5 has another type of rule, called
a MaintainRule, which attempts to make a maintained relation behave just
like a defined relation. This will enable users to start out by defining
relations and later optimize their programs by changing them to maintained
relations.
The only difference between defined and maintained relations (other than
performance) is that
defined relations can have update macros that say how the relations in
their definitions should be altered to change the truth value of the
definition. Of course a maintained relation has to be stored, so its
update macros have to describe how its stored value is changed. If you
want to maintain a defined relation with update macros, you'll have to
divide it into two relations: R-Maintained, with the original
definition, and R, which is defined as R-Maintained and has the update
macro. Then you can use R as before, but with different performance.
Note:
Some versions of AP5 may assume (and not check) that maintained
definitions are not circular.
AP5 has a consistency rule that causes relations to be maintained whenever
they have an implementation other than DEFINED and a definition. ("Defined"
relations, i.e., those that are always derived from definitions, also have
definitions, but their implementation is DEFINED.) The relation
Relation (relation-in-defn rel rel-or-rule)
means that rel is a relation that
appears in the wffdefn of rel-or-rule (if it's a relation) or the
ruletrigger of rel-or-rule (if it's a rule). The term "appears in"
includes macro expansion but not expansion of defined relations.
Thus if we define R2 in terms of R1 and R3 in terms of R2, this relates
R2 to R3 but not R1 to R3. For that you should use
Relation (relation-in-defn* rel rel-or-rule)
which is the transitive closure.
Note:
While this all reacts properly to changes in wffdefn's, it is
assumed that macro definitions are stable in the sense that you don't
change macros used in ruletriggers/wffdefns in such a way that they
expand into a different set of relations. If you really must do this
you should probably undeclare those rules/relations first and then
redeclare them. I'm afraid the only way to find out what definitions
use a macro is to iterate over all the definitions.
Maintained relations are stored in the manner specified by their
implementations. For example, if you do
(DefRelation PersonPhone :implementation structprop
:definition
((x y) s.t. (E (office) (and (PersonOffice x office)
(OfficePhone office y)))))
you will get a structprop relation which tracks the definition.
The relation
Relation (maintainedrel relation defn rule)
means that the rule maintains the equivalence between the relation and
the definition. (In fact, MaintainedRel is a maintained relation!)
The rule that maintains a relation is of type
Type (maintainrule x)
These rules always assume that, at the time of rule declaration, the
relation matches the definition, i.e., that the relation need only be
maintained incrementally. Usually this requires only that the
definition be declared before any updates that make it true, e.g., in
the PersonPhone example, before any PersonOffice or OfficePhone tuples
are added. By default, AP5 will generate an error if you try to
directly update a maintained relation, since this is supposed to be
done only be the maintaining rule. However, if you discover that the
maintained relation is wrong (for example if you failed to insert a
tuple before asking that the relation be maintained), you can update
it with
Variable ap5::fix-maintained-relation
bound to a non-NIL value.
Miscellaneous
First come those things that are so miscellaneous that they don't even
belong in one of the following sections:
The object of a high level compiler like AP5 is that you shouldn't have
to think about the algorithms it chooses. In general, this is all right
if the computations will be cheap. AP5 tries to warn you when this is
not the case. In particular:
Variable ap5::geneffortwarning
holds a number (or NIL, meaning infinite). Whenever a generator
is compiled with an estimated effort of this amount or more, a
warning is printed.
Variable ap5::gensizewarning
is similar, but causes warnings when the estimated number of tuples is large.
Variable ap5::triggereffortwarning
is a value to which ap5::GenEffortWarning is rebound when compiling trigger
code. Since triggering code may run on any atomic transition, the default
value is somewhat lower than that of ap5::GenEffortWarning.
Variable ap5::triggersizewarning
is the analagous variable for ap5::GenSizeWarning.
Variable ap5::prototype-mode
If this is NIL (the default), the results of AP5's compilation (into
lisp code) are further compiled by the lisp compiler. If you don't
actually expect to run that code very much you can save compilation
time by turning on this switch.
More esoteric wff syntax
By default DefRelation declares a macro for the name of the relation
being declared (unless it already has a function or macro definition).
We will refer to this as a relation macro.
If we define a binary relation R, then we get the following macro expansions:
(R x y) => (?? R x y)
(R x) => (any y s.t. (R x y))
(R) => (any x y s.t. (R x y))