IPython / Jupyter REPL¶
Clausal has first-class IPython integration that turns an IPython session into a
Prolog-style REPL. Queries use a *(goals) syntax, uppercase names are
automatically treated as logic variables, and solutions are presented
interactively one at a time — separated by or, just as Clausal's disjunction
operator reads. For notebook-specific rendering, see Jupyter Notebooks.
Quick start¶
After setting up the IPython startup hook (one-time, per profile):
# Cell 1 — load a module
import clausal.examples.sudoku as sudoku
# Cell 2 — query
*(sudoku.Problem(1, ROWS), sudoku.Sudoku(ROWS))
ROWS is declared automatically as a fresh logic variable — no Var() needed.
Set up the IPython startup hook¶
IPython parses each cell before executing any code in it, so the AST
transformer that rewrites -import_from(...) and *(...) queries has to
be registered before your first cell runs. The cleanest way is an
IPython startup file that imports Clausal once at session start.
Recommended — install command¶
This drops a single file at
~/.ipython/profile_default/startup/00-clausal.py. Every IPython session
in that profile picks it up automatically; you can delete the file to opt
back out. Useful flags:
clausal-install-ipython --profile myprofile # different IPython profile
clausal-install-ipython --force # overwrite existing file
clausal-install-ipython --uninstall # remove the hook
clausal-install-ipython --print # print the script to stdout
Manual — write the file yourself¶
If you'd rather not run the install command, the hook is a five-line file you can copy in by hand:
# ~/.ipython/profile_default/startup/00-clausal.py
try:
from clausal.import_hook import enable_ipython as _enable_clausal
_enable_clausal(get_ipython().user_ns)
except ImportError:
pass
Per-session — call enable_ipython from cell 1¶
Without a startup file, every session needs an explicit setup cell:
After that, subsequent cells can use *(...) and -import_from(...).
Environment-variable opt-in (advanced)¶
Setting CLAUSAL_IPYTHON=1 makes import clausal itself register the
AST transformer — handy if your workflow already imports clausal at the
top of every session, but on its own it doesn't help: until something
imports clausal, IPython has no way to know about the integration. Use
this in combination with a startup file or a --ext IPython extension
that imports clausal during startup:
Query syntax¶
There are two ways to query in IPython:
Plain Python iteration¶
Since predicate terms are directly iterable, you can use a standard for loop:
from clausal import Var
from hello import greeting
for trail in greeting(X := Var()):
print(X.value)
This works identically in plain Python scripts and IPython.
*(goals) — IPython-specific shorthand¶
The *(goals) form is an IPython-specific shorthand that auto-declares
uppercase variables and displays solutions interactively. It is rewritten at
the text level into a valid Python call before compilation (for compatibility
with Python 3.13+).
The entire expression inside *(...) is treated as a clause body, not
ordinary Python. Operators that have special meaning in Python are rewritten
into Clausal goals:
Inside *(...) |
Goal constructed |
|---|---|
X is Y |
Unify(left=X, right=Y) |
X == Y |
ArithEq(left=X, right=Y) |
X != Y |
ArithNeq(left=X, right=Y) |
X < Y |
Lt(left=X, right=Y) |
A and B |
And(left=A, right=B) |
A or B |
Or(left=A, right=B) |
Single goal¶
Conjunction — comma-separated¶
Multiple comma-separated goals are folded into a left-associative And chain
and share a single solver context for correct backtracking.
Unification goal¶
is inside *(...) is unification, not Python identity.
Variable auto-declaration¶
Any uppercase name inside a *(...) query is automatically allocated as a
fresh Var() via a walrus assignment embedded in the goal expression itself:
This mirrors Prolog's convention that uppercase identifiers are variables. If
you need to share a variable across multiple cells, declare it explicitly with
X = Var() before the query.
Interactive solution browsing¶
Solutions are presented one at a time with an or separator between them,
matching Clausal's disjunction syntax:
Key bindings follow standard Prolog REPL conventions:
| Key | Action |
|---|---|
SPACE, n |
next solution |
ENTER, . |
stop (commit to current solution) |
ESC, q |
abort (no output) |
a |
show all remaining solutions |
Terminal colours¶
in_ a terminal IPython session (ipython command, not Jupyter), ANSI colours
are enabled automatically. Unbound variables, atoms, numbers, strings, and
brackets each get a distinct colour with rainbow bracket-depth cycling.
Control the style from any cell using the injected helpers:
# Disable colours
set_style(TermStyle())
# Re-enable default colours
set_style(TermStyle(colors=ANSI_COLORS))
# Change the anonymous-variable symbol (default: '_')
set_style(TermStyle(anon_var='?'))
# Custom colour scheme
set_style(TermStyle(colors={
'number': '\033[33m',
'string': '\033[32m',
'atom': '\033[36m',
'var': '\033[35m',
'brackets': ['\033[91m', '\033[93m', '\033[92m', '\033[96m', '\033[94m', '\033[95m'],
'reset': '\033[0m',
}))
set_style, TermStyle, and ANSI_COLORS are automatically injected into the
IPython namespace by enable_ipython. Colours are not enabled in Jupyter
kernels, which render output as HTML rather than a terminal.
Using Solutions directly¶
For programmatic use, wrap any iterator of binding dicts:
from clausal import Var, call, Solutions
from clausal.logic.variables import walk, Trail
ROWS = Var()
trail = Trail()
def gen():
for _ in call(sudoku.Problem, 1, ROWS, trail=trail):
for _ in call(sudoku.Solve, ROWS, trail=trail):
yield {'ROWS': walk(ROWS)}
Solutions(gen())
Solutions also accepts a predicate instance directly:
call with predicate classes¶
call() accepts a predicate class directly — no module= argument needed:
String functor names still work when a module is provided: