Jupyter Notebooks¶
Clausal Prolog integrates with Jupyter notebooks — query logic programs interactively
from notebook cells and see results rendered with syntax colouring. The
*(goals) query syntax and automatic variable declaration work the same as in
terminal IPython; the difference is that solutions are rendered as styled HTML
instead of a terminal-interactive keypress loop.
Setup¶
One-time setup in the first cell of your notebook:
Or set CLAUSAL_IPYTHON=1 in your environment to auto-enable on import:
Your first query¶
Import a module and query it with the *(goals) syntax:
This displays N is 21 with syntax colouring. (The goal-position seam,
for N in --fib.fib(8, N):, is for Python hosted in a .seam file
and does not work in a notebook cell; see IPython.) Uppercase names like N are
automatically allocated as fresh logic variables.
How solutions display¶
In a Jupyter notebook, all solutions are rendered at once (up to a configurable limit) as styled HTML. Each solution is separated by or:
Displays:
X is 1
or
X is 2
or
X is 3
No more solutions.
In terminal IPython, the same code uses interactive keypresses instead.
Solution limits¶
By default, at most 20 solutions are shown in a Jupyter notebook. This prevents accidentally rendering thousands of results from a large search space.
Override per-query by building the Solutions yourself — it wraps any
iterator of binding dicts — and passing limit. The goal is a cell run
against a module with solve:
from clausal import Module, Var, solve, Solutions
X = Var()
Solutions(({"X": X.value}
for _ in solve(("in_", X, list(range(100))), module=Module("scratch"))),
limit=5)
Or change the global default:
Worked examples¶
The following examples are written in seam syntax (.seam). Each block
is automatically tested by the documentation test runner.
Family tree¶
parent('alice', 'bob'),
parent('alice', 'carol'),
parent('bob', 'dave'),
parent('bob', 'eve'),
grandparent(GP, GC) <- (
parent(GP, MID),
parent(MID, GC)
)
test("alice is grandparent of dave") <- (grandparent('alice', 'dave'))
test("alice is grandparent of eve") <- (grandparent('alice', 'eve'))
In a notebook you would query this as:
List membership and append¶
member(X, [X, *_]),
member(X, [_, *REST]) <- member(X, REST)
my_append([], YS, YS),
my_append([H, *XS], YS, [H, *ZS]) <- my_append(XS, YS, ZS)
test("member finds element") <- (member(2, [1, 2, 3]))
test("append two lists") <- (my_append([1, 2], [3, 4], [1, 2, 3, 4]))
Recursive length¶
my_len([], 0),
my_len([_, *T], N) <- (
my_len(T, N1),
N == N1 + 1
)
test("length of empty list") <- (my_len([], 0))
test("length of three-element list") <- (my_len(['a', 'b', 'c'], 3))
Accumulator pattern¶
sum_list([], 0),
sum_list([H, *T], S) <- (
sum_list(T, S1),
S == S1 + H
)
test("sum of 1..4 is 10") <- (
sum_list([1, 2, 3, 4], S),
S == 10
)
test("sum of empty is 0") <- (
sum_list([], S),
S == 0
)
Fibonacci¶
fib(0, 0),
fib(1, 1),
fib(N, F) <- (
N > 1,
N1 == N - 1,
N2 == N - 2,
fib(N1, F1),
fib(N2, F2),
F == F1 + F2
)
test("fib(0) = 0") <- (fib(0, 0))
test("fib(5) = 5") <- (fib(5, 5))
test("fib(8) = 21") <- (fib(8, 21))
Differences from terminal IPython¶
| Feature | Terminal IPython | Jupyter Notebook |
|---|---|---|
| Solution display | One at a time, key-driven | All at once (up to limit) |
| Interaction | SPACE/n/ENTER/ESC keys | No interactive keys |
| Colours | ANSI escape codes | CSS spans |
| Default limit | Unlimited (manual stepping) | 20 solutions |
See IPython / Jupyter for the full reference on the *(goals)
syntax, variable auto-declaration, and the Solutions class API.