Clausal Prolog¶
Clausal Prolog is the language of .clausal files: ISO Prolog syntax,
ISO builtin names and ISO error terms, without cut. It is the default
surface for writing logic programs, and the project is working towards ISO
Prolog conformity. Where ISO is silent, Clausal Prolog follows
Scryer Prolog.
The engine reads three source surfaces:
| Extension | Surface | Syntax | Read by |
|---|---|---|---|
.clausal |
Clausal Prolog | ISO Prolog, cut-free | the native ISO front end, always |
.pl |
ISO Prolog | Regular ISO Prolog, cut included | external Prolog systems (the Scryer and Trealla embeddings); experimental in-process import via CLAUSAL_PL_FRONTEND |
.seam |
the seam | Python syntax | PredicateLoader |
The seam is the boundary with Python. It is where the ++expr escape,
hosted Python statements and the adapters over Python libraries live. Most
of the language pages in these docs still show seam syntax, and they say
so. The semantics they describe (unification, search, tabling, constraints,
modules) are shared by all three surfaces.
The extension flip
Before 1.0, .clausal was the Python-syntax seam language. It is now
Clausal Prolog, and seam source is named .seam. A seam file still
named .clausal fails to load with a Prolog syntax error. To migrate,
run git mv name.clausal name.seam. Move the file rather than copying
it: a stale name.clausal beside name.seam is a different module,
written in Prolog. In one directory, name.seam beats name.clausal,
which beats name.pl.
A module¶
% family.clausal
:- module(family, [grandparent/2, fib/2, path/2]).
:- use_module(library(clpz)).
parent(tom, bob).
parent(tom, liz).
parent(bob, ann).
parent(bob, pat).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
% CLP(Z) arithmetic: fib/2 runs in both directions.
fib(0, 0).
fib(1, 1).
fib(N, F) :-
N #> 1,
N1 #= N - 1, N2 #= N - 2,
F #>= N1, F #= F1 + F2,
fib(N1, F1), fib(N2, F2).
:- table(path/2).
edge(1, 2).
edge(2, 3).
edge(3, 1).
path(X, Y) :- edge(X, Y).
path(X, Y) :- edge(X, Z), path(Z, Y).
test("tom's grandchildren") :- grandparent(tom, ann), grandparent(tom, pat).
test("fib(10) = 55") :- fib(10, 55).
test("which fib is 55?") :- once(fib(N, 55)), N == 10.
test("path reaches the whole cycle") :- findall(Y, path(1, Y), Ys), msort(Ys, [1, 2, 3]).
test("tom has no grandparent", fail) :- grandparent(_, tom).
:- end_module(family).
Query it from Python:
Python asks the questions from a .seam file, with a goal in for
position after --:
# app.seam
-import_from(family, [grandparent, fib])
-private([tom])
for GRANDCHILD in --grandparent(tom, GRANDCHILD):
print(GRANDCHILD) # ann, then pat
for N in --fib(N, 55):
print(N) # 10
break
The goal's variables become ordinary Python locals, bound to each answer
in turn. Atoms used in a .seam file are declared, here with -private.
Run its tests with python -m clausal.testing family.clausal, or let
pytest collect them (see Testing).
Libraries, constraints and DCGs¶
Load libraries with use_module/1,2. Grammar rules use --> and run with
phrase/2,3.
% puzzles.clausal
:- module(puzzles, [greeting//0, send_more/1, classify/2]).
:- use_module(library(clpz)).
:- use_module(library(reif)).
greeting --> [hello], name.
name --> [world].
name --> [prolog].
send_more([S,E,N,D,M,O,R,Y]) :-
Vars = [S,E,N,D,M,O,R,Y],
Vars ins 0..9, all_different(Vars),
S #\= 0, M #\= 0,
1000*S + 100*E + 10*N + D + 1000*M + 100*O + 10*R + E #=
10000*M + 1000*O + 100*N + 10*E + Y,
label(Vars).
classify(X, R) :- if_(X = a, R = yes, R = no).
test("dcg") :- phrase(greeting, [hello, prolog]).
test("send more money") :- send_more([9,5,6,7,1,0,8,2]).
test("if_") :- classify(a, yes), classify(b, no).
test("dif") :- dif(X, a), X = b.
test("double quotes are strings") :- X = "abc", string(X).
:- end_module(puzzles).
The rules¶
Clausal Prolog is ISO Prolog with a few deliberate rules. Each one is checked when the file loads, and a violation is a load error naming the file and line.
Cut-free¶
! and -> (if-then, and if-then-else) are refused. *-> (soft cut) is
not an operator in Clausal Prolog, so writing it is a syntax error:
c.clausal:2: `!` (cut) is refused: Clausal is cut-free with no committed
choice, by design (ruling): !, -> and *-> are refused
Use the pure alternatives instead. These are dif/2, if_/3 and the
reified predicates from library(reif) (see
If-Then-Else), constraints, once/1, and first-argument
indexing (see Purity). The same rule applies to {!} in a
grammar body.
Modules end with end_module/1¶
A module file (one with :- module(Name, Exports).) must end with
:- end_module(Name).. Only comments and layout may follow it. A missing
directive is the ISO error
error(existence_error(directive, end_module(Name)), load/1).
Whether it is required is decided by, most specific first:
- the file's own
:- set_prolog_flag(require_end_module, true|false).; - the process-wide setting:
set_prolog_flag(require_end_module, V)run as a goal, or the environment variableCLAUSAL_REQUIRE_END_MODULE; - the surface default: required for
.clausal, not required for.pl.
See Importing Prolog for the full error table.
No imports of ISO Prolog¶
A .clausal module may not import a .pl module, because a .pl module
may use cut. The refusal is
permission_error(access, prolog_module, M). It applies to use_module
and to the run-time routes (M:G, call/N of a qualified goal). The
dependency is one-way: a .pl module may import a .clausal one. To reuse
.pl code, convert it to .clausal, or wrap it in a .seam module.
Python only through the seam¶
Clausal Prolog reaches Python only through:
- engine-shipped modules: the
library(...)facades over the engine's Python modules (library(json),library(datetime),library(units), …), the standard library and the engine's own adapters; - Python-free
.seammodules: Clausal code written in seam syntax. These are checked transitively; - Python bridges:
.seammodules that do run Python, listed in[tool.clausal] python_bridgesof the nearestpyproject.tomlabove the importing.clausalfile, optionally pinned by sha256.
:- use_module(library(datetime), [date_add/3]). % a facade: allowed
:- use_module(helpers, [double/2]). % Python-free helpers.seam: allowed
Anything else is refused at load with
permission_error(import, python_bridge, M), and a direct import of a
Python module by path with permission_error(access, python_module, M).
See Importing Prolog: Python bridges and
Python Integration.
Differences from the seam¶
If you know the seam syntax from the other pages of these docs, these are the spellings that change:
Seam (.seam) |
Clausal Prolog (.clausal) |
|
|---|---|---|
| Fact | parent(tom, bob), |
parent(tom, bob). |
| Rule | p(X) <- (q(X), r(X)) |
p(X) :- q(X), r(X). |
| Variables | ALL_CAPS |
ISO: X, Rest, _ |
| Arithmetic | N1 == N - 1 |
N1 #= N - 1 (CLP(Z), preferred; works in both directions), or ISO N1 is N - 1 |
| Unify | X is Y |
X = Y |
| Module | -module(m, [p(X)]) |
:- module(m, [p/1]). … :- end_module(m). |
| Import | -import_from(lib, [p]) |
:- use_module(lib, [p/1]). |
| Table | -table(path/2) |
:- table(path/2). |
| Grammar rule | s >> (np, vp) |
s --> np, vp. |
| Test | test("t") <- goal |
test("t") :- goal. |
| Python | ++expr, hosted Python |
not available: go through a .seam module |
Tools¶
python -m clausal.testingand the pytest plugin runtest/1andtest/2clauses in.clausalfiles (Testing).clausal-fmtandclausal-rewritework on.seamfiles only, and refuse Prolog-syntax files.- To run unrestricted ISO Prolog (with cut) alongside Clausal Prolog, use the Scryer or Trealla embeddings.