Cookbook¶
Task-oriented recipes that combine the query API, the rule DSL, and the builtin goals. Each recipe is self-contained. For the reference behind them see Querying, Builtin Goals, and Rule DSL.
All snippets assume you are in the cl-prolog package:
Collect all children of a parent¶
Use findall/3 to gather every solution of a subgoal into a list:
(define-rulebase *family*
((parent tom bob))
((parent tom liz))
((parent bob alice)))
(query-prolog *family* '(findall ?child (parent tom ?child) ?children))
;; => (((?CHILDREN BOB LIZ)))
bagof/3 and setof/3 are the grouping and sorted-deduplicated variants.
Count solutions¶
Combine findall/3 with length/2 to count without writing a counter:
(query-prolog *family*
'(and (findall ?c (parent tom ?c) ?cs)
(length ?cs ?n)))
;; ?N is bound to the number of children of tom
Test membership without enumerating¶
memberchk/2 commits to the first match, so it answers a yes/no membership
question without leaving choice points:
Use plain member/2 when you want to enumerate every matching element.
Run append/3 backwards¶
Relational predicates work in every mode. Splitting a list into all
prefix/suffix pairs is just append/3 with the whole list fixed:
Generate a numeric range¶
numlist/3 from library(lists) builds an ascending integer list:
Sum it with sum_list/2:
Map a goal across a list¶
maplist/N from library(apply) applies a goal across one to four lists in
lockstep. Pair it with a foreign predicate or a user rule:
(define-rulebase *db*
((double 1 2))
((double 2 4))
((double 3 6)))
(query-prolog *db* '(maplist double (1 2 3) ?doubled))
;; => (((?DOUBLED 2 4 6)))
foldl/N threads an accumulator; include/3, exclude/3, and partition/4
filter by whether the goal succeeds on each element.
Filter with a guard¶
A :when guard fires a rule only when a Lisp condition over bound values
holds:
(define-rulebase *scores*
((score bob 42))
((score amy 8))
((score cal 25))
((passing ?who) (score ?who ?n) (:when (>= ?n 20))))
(query-prolog *scores* '(passing ?who))
;; => (((?WHO . BOB)) ((?WHO . CAL)))
See Arithmetic and Comparison for the guard vs. #=/2 vs.
is/2 distinctions.
Extend a rulebase without mutating it¶
extend-rulebase returns a new rulebase with additional clauses, leaving the
base untouched:
(define-rulebase *base*
((color apple red)))
(defparameter *extended*
(extend-rulebase *base*
((color lime green))))
(query-prolog *extended* '(color ?fruit ?color)) ; sees both
(query-prolog *base* '(color ?fruit ?color)) ; sees only apple
Mutate a rulebase deliberately¶
When you do want mutation, the dynamic-database goals insert and retire clauses in the rulebase passed to the query:
(query-prolog *family* '(assertz (parent tom eve)))
(query-prolog *family* '(parent tom ?child)) ; now includes EVE
Born/died revisions give logical-update semantics: a running query proceeds
over its snapshot even as the database changes. Use copy-rulebase first when
you need isolation. See Architecture.
Bind variables from the first solution¶
with-prolog-query destructures the first solution's bindings into Lisp
variables; prolog-match dispatches over queries like cond:
Bound (streaming) search over a large space¶
For a potentially large search, stream solutions and stop early rather than collecting them all:
(map-prolog-solutions
(lambda (solution)
(format t "~&=> ~S~%" (solution-binding '?who solution)))
*family* '(ancestor tom ?who))
Or cap the count directly with :limit:
Bound recursion depth¶
:max-depth bounds user-rule expansion so a runaway recursive rule fails loudly
with prolog-depth-limit-exceeded instead of looping:
See Semantics and Conditions and Errors for what each depth value means.