Extending the Engine¶
cl-prolog exposes exactly one supported extension point: foreign
predicates, defined with define-foreign-predicate. A foreign predicate is a
Lisp function that participates in proof search using the engine's
continuation-passing emit protocol. The internal builtin-definition machinery
is deliberately not exported — foreign predicates are the public surface.
The shape of a foreign predicate¶
(name argument...)is the predicate indicator. The argument list has fixed arity; lambda-list keywords such as&restare not supported. One definition registers exactly one name/arity pair.rulebase,environment, anddepthexpose the current solver context.emitis the continuation. Call it once per solution, passing the environment that represents that solution. Call it zero times to fail. Never collect results into a list — stream them throughemit.
A worked example: twice/2¶
twice/2 succeeds when its second argument is double the first:
(define-foreign-predicate (twice input output)
(rulebase environment depth emit)
(declare (ignore rulebase depth))
(let ((value (logic-substitute input environment)))
(when (numberp value)
(multiple-value-bind (extended ok) (unify output (* 2 value) environment)
(when ok (funcall emit extended))))))
Step by step:
logic-substituteapplies the current environment toinput, resolving any bound variables to their values.- The
when (numberp value)guard means the predicate simply fails (emits nothing) ifinputis not yet a number. unifyattempts to bindoutputto(* 2 value). It returns two values: the extended environment and a success flag.- On success,
emitis called once with the extended environment — that is one solution.
Emitting many solutions¶
Because emit is a continuation, a predicate can produce several solutions by
calling it more than once. choose/1 emits two:
(define-foreign-predicate (choose output) (rulebase environment depth emit)
(declare (ignore rulebase depth))
(dolist (value '(left right))
(multiple-value-bind (extended ok) (unify output value environment)
(when ok (funcall emit extended)))))
Querying (choose ?x) then proves twice, binding ?x to left and right.
The unify contract¶
unify is the workhorse for foreign predicates:
(unify left right &optional environment)
;; => (values extended-environment t) on success
;; => (values nil nil) on failure
The occurs check is always enabled, so unification never introduces cyclic
terms. Environments are persistent association lists: unify extends rather
than mutates, so older environments remain valid choice points. See the
API Reference for unify, logic-substitute,
logic-var-p, and fresh-logic-variable.
Dispatch rules¶
- Foreign predicates dispatch by exact name and arity. If your predicate does not run, check both parts of the indicator — see Troubleshooting.
- Registered builtins remain authoritative when names overlap: you cannot shadow a builtin with a foreign predicate of the same indicator.
- Calling a known name at an unsupported arity denotes a different, undefined
procedure and signals an ISO
existence_error.
Guards are not foreign predicates¶
The (:when expression) guard in the Rule DSL is a lighter
mechanism: the DSL macros compile the expression into a precompiled closure
invoked as a (:when function variable...) goal. Guards are for pure Lisp
predicates over already-bound values; foreign predicates are for goals that
must unify outputs or emit multiple solutions. See
Architecture for how guards are compiled.