XSB provides an array of features for modifying the dynamic database. Using assert/1, clauses can be asserted using first-argument indexing in a manner that is now standard to Prolog implementations. While this is the default behavior for XSB, other behavior can be specified using the (executable) directives index/3 and index/2. For instance, dynamic clauses can be declared to have multiple or joint indexes, while this indexing can be either hash-based as is typical in Prolog systems or based on tries. No matter what kind of indexing is used, space is dynamically allocated when a new clause is asserted and, unless specified otherwise, released when it is retracted. Furthermore, the size of any index table expands dynamically as clauses are asserted.
Consider first dynamic predicates that use traditional hash-based indexing. XSB asserts WAM code for such clauses, leading to execution times similar to compiled code for unit and binary clauses. Furthermore, tabling can be used with a dynamic predicate by explicitly declaring a predicate to be both dynamic and tabled. For clauses that are asserted as WAM code, the ``immediate semantics'' of dynamic predicates is used, not the so-called ``logical semantics'' of assert/retract [33]. This means that significant care must be taken when modifying the definition of a predicate which is currently being executed. Notice that this makes some operations difficult. For example, one might try to retract from dynamically asserted predicates, p/1 and q/1, exactly their intersection, by issuing the following query:
Asserting clauses as WAM code might be considerably slow for some applications. To remedy this, XSB provides an alternative to assert/1 which implements assert's functionality using the trie-based tabling data structures [39]. Though trie-based dynamic code can be created (and usually executed) significantly faster than using assert/1, users of the following predicates should be aware that trie-based assert can be used only for unit clauses where a relation is viewed as a set, and where the order of the facts is not important.
XSB does not at this time fully support dynamic predicates defined within compiled code. The only way to generate dynamic code is by explicitly asserting it, or by using the standard predicate load_dyn/1 to read clauses from a file and assert them (see the section Asserting Dynamic Code in Volume 2). There is a dynamic/1 predicate (see page ) that declares a predicate within the system so that if the predicate is called when no clauses are presently defining it, the call will quietly fail instead of issuing an ``Undefined predicate'' error message.
In general, XSB supports hash-based indexing on various arguments or combinations of arguments, along with trie-based indexing. The availability of various kinds of indexing depends on whether code is static (e.g. compiled) or dynamic (e.g. asserted or loaded with load_dyn/1). The executable directive index/2 does not re-index an already existing predicate but takes effect only for clauses asserted after the directive has been given. Index directives can be given to the compiler as part of source code or executed during program execution (analogously to op/3).
An index is usually on a single argument, in which case the IndexElt consists of a single argument indicator. However, sometimes one wants an index on multiple arguments, meaning that the values of several arguments are to be concatenated to create the search key of the index. Such a multi-argument (or joint) index is indicated by using an IndexElt that has up to 3 argument indicators separated by the + (plus) operator, e.g., 1+2+3.
For example, index(p/3,[2,1]) indicates that clauses asserted to the predicate p/3 should be indexed on both the second and the first argument. Subsequent calls to p/3 will first check to see if the second argument is nonvariable, and if so use that index, using the main functor symbol of that argument. If the second argument is variable, it will next check to see if the first argument is nonvariable and if so, use that index, built on the main functor symbol of the first argument.
index(p/3,[*(2),1]) would result in similar behavior as the previous example, but the first index to be tried (on the second argument) would be built using more of the term value in that second argument position (not just the main functor symbol.)
As another example, one could specify: index(p/5,[1+2,1,4]). After clauses are asserted to it, a call to p/5 would first check to see if both the first and second arguments are nonvariable and if so, use an index based on both those values. Otherwise, it would see if the first argument is nonvariable and if so, use an index based on it. Otherwise, it would see if the fourth argument is nonvariable and if so use an index based on it. As a last resort, it would use no index but backtrack through all the clauses in the predicate. In all these cases, the indexes are built using only the main functor symbol in the indicated argument position. (Notice that it may well make sense to include an argument that appears in a joint specification later alone, as 1 in this example, but it never makes sense forcing the single argument to appear earlier. In that case the joint index would never be used.)
If we want to use similar indexing on p/5 of the previous example, except say argument 1 takes on complex term values and we want to index on more of those terms, we might specify the index as index(p/5,[*(1)+2,*(1),4]).
Despite these advantages, representing terms as tries leads to semantic differences from asserted code, of which the user should be aware. First, the order of clauses within a trie is arbitrary: using asserta/1 or assertz for a predicate currently using trie indexing will give the same behavior as using assert. Also, the current version of XSB only allows trie indexing for unit clauses.
Trie-based indexing is available only for dynamic predicates.