910 lines
36 KiB
Markdown
910 lines
36 KiB
Markdown
# Scryer Prolog
|
||
|
||
Scryer Prolog aims to become to ISO Prolog what GHC is to Haskell: an open
|
||
source industrial strength production environment that is also a
|
||
testbed for bleeding edge research in logic and constraint
|
||
programming, which is itself written in a high-level language.
|
||
|
||
**Scryer Prolog passes all tests** of
|
||
[syntactic conformity](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing),
|
||
[`variable_names/1`](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/variable_names) and
|
||
[`dif/2`](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/dif).
|
||
|
||
The homepage of the project is: [**https://www.scryer.pl**](https://www.scryer.pl)
|
||
|
||

|
||
|
||
## Phase 1
|
||
|
||
Produce an implementation of the Warren Abstract Machine in Rust, done
|
||
according to the progression of languages in [Warren's Abstract
|
||
Machine: A Tutorial Reconstruction](https://github.com/mthom/scryer-prolog/blob/master/wambook/wambook.pdf).
|
||
|
||
Phase 1 has been completed in that Scryer Prolog implements in some form
|
||
all of the WAM book, including lists, cuts, Debray allocation, first
|
||
argument indexing, last call optimization and conjunctive queries.
|
||
|
||
## Phase 2
|
||
|
||
Extend Scryer Prolog to include the following, among other features:
|
||
|
||
- [x] call/N as a built-in meta-predicate.
|
||
- [x] ISO Prolog compliant throw/catch.
|
||
- [x] Built-in and user-defined operators of all fixities, with custom
|
||
associativity and precedence.
|
||
- [x] Bignum, rational number and floating point arithmetic.
|
||
- [x] Built-in control operators (`,`, `;`, `->`, etc.).
|
||
- [x] A revised, not-terrible module system.
|
||
- [x] Built-in predicates for list processing and top-level declarative
|
||
control (`setup_call_cleanup/3`, `call_with_inference_limit/3`,
|
||
etc.)
|
||
- [x] ~~Default representation of strings as lists of characters, using a packed
|
||
internal representation.~~
|
||
- [x] `term_expansion/2` and `goal_expansion/2`.
|
||
- [x] Definite Clause Grammars.
|
||
- [x] Attributed variables using the SICStus Prolog interface and
|
||
semantics. Adding coroutines like `dif/2`, `freeze/2`, etc.
|
||
is straightforward with attributed variables.
|
||
- [x] Support for `verify_attributes/3`
|
||
- [x] Support for `attribute_goals/2` and `project_attributes/2`
|
||
- [x] `call_residue_vars/2`
|
||
- [x] `if_/3` and related predicates, following the developments of the
|
||
paper "[Indexing `dif/2`](https://arxiv.org/abs/1607.01590)".
|
||
- [x] All-solutions predicates (`findall/{3,4}`, `bagof/3`, `setof/3`, `forall/2`).
|
||
- [x] Clause creation and destruction (`asserta/1`, `assertz/1`,
|
||
`retract/1`, `abolish/1`) with logical update semantics.
|
||
- [x] Backtrackable and non-backtrackable global variables via `bb_get/2`
|
||
`bb_put/2` (non-backtrackable) and `bb_b_put/2`
|
||
(backtrackable).
|
||
- [x] Delimited continuations based on reset/3, shift/1 (documented in
|
||
"[Delimited Continuations for Prolog](https://biblio.ugent.be/publication/5646080/file/5646081)").
|
||
- [x] Tabling library based on delimited continuations
|
||
(documented in "[Tabling as a Library with Delimited Control](https://biblio.ugent.be/publication/6880648/file/6885145.pdf)").
|
||
- [x] A _redone_ representation of strings as difference lists of
|
||
characters, using a packed internal representation.
|
||
- [x] clp(B) and clp(ℤ) as builtin libraries.
|
||
- [x] Streams and predicates for stream control.
|
||
- [x] A simple sockets library representing TCP connections as streams.
|
||
- [x] Incremental compilation and loading process, newly written,
|
||
primarily in Prolog.
|
||
- [ ] Improvements to the WAM compiler and heap representation:
|
||
- [ ] Replacing choice points pivoting on inlined semi-deterministic predicates
|
||
(`atom`, `var`, etc) with if/else ladders. (_in progress_)
|
||
- [ ] Inlining all built-ins and system call instructions.
|
||
- [x] Greatly reducing the number of instructions used to compile disjunctives.
|
||
- [ ] Storing short atoms to heap cells without writing them to the atom table.
|
||
- [ ] A compacting garbage collector satisfying the five properties of
|
||
"[Precise Garbage Collection in Prolog](https://www.complang.tuwien.ac.at/ulrich/papers/PDF/2008-ciclops.pdf)." (_in progress_)
|
||
- [ ] Mode declarations.
|
||
|
||
## Phase 3
|
||
|
||
Use the WAM code produced by the completed code generator to get
|
||
JIT-compiled and -executed Prolog programs. The question of how to get
|
||
assembly from WAM code is something I'm still considering.
|
||
|
||
It's my hope to use Scryer Prolog as the logic engine of a low level (and
|
||
ideally, very fast) [Shen](http://shenlanguage.org) implementation.
|
||
|
||
## Nice to have features
|
||
|
||
There are no current plans to implement any of these, but they might be
|
||
nice to have in the future. They'd make a good project for anyone wanting
|
||
to contribute code to Scryer Prolog.
|
||
|
||
1. Implement the global analysis techniques described in Peter van
|
||
Roy's thesis, "[Can Logic Programming Execute as Fast as Imperative
|
||
Programming?](https://www.info.ucl.ac.be/~pvr/Peter.thesis/Peter.thesis.html)"
|
||
|
||
2. Add unum representation and arithmetic, using either an existing
|
||
unum implementation or an ad hoc one. Unums are described in
|
||
Gustafson's book "[The End of Error](http://www.johngustafson.net/unums.html)."
|
||
|
||
3. Add concurrent tables to manage shared references to atoms and
|
||
strings.
|
||
|
||
4. Add some form of JIT predicate indexing.
|
||
|
||
## Installing Scryer Prolog
|
||
|
||
### Binaries
|
||
|
||
Precompiled binaries for several platforms are available for download
|
||
at:
|
||
|
||
**https://github.com/mthom/scryer-prolog/releases/latest**
|
||
|
||
### Native Compilation
|
||
|
||
First, install the latest stable version of
|
||
[Rust](https://www.rust-lang.org/tools/install) using your
|
||
preferred method. Scryer tends to use features from newer Rust
|
||
releases, whereas Rust packages in Linux distributions, Macports,
|
||
etc. tend to lag behind. [rustup](http://rustup.rs) will keep your
|
||
Rust updated to the latest stable release; any existing Rust
|
||
distribution should be uninstalled from your system before rustup is
|
||
used.
|
||
|
||
|
||
> [!NOTE]
|
||
> The minimum rust toolchain version required can be found in the [Cargo.toml](https://github.com/mthom/scryer-prolog/blob/master/Cargo.toml#L13)
|
||
under the `package.rust-version` key.
|
||
> The accuracy of this value is validated in CI
|
||
|
||
### From a local git checkout
|
||
|
||
```
|
||
$> git clone https://github.com/mthom/scryer-prolog
|
||
$> cd scryer-prolog
|
||
$> cargo build --release
|
||
```
|
||
|
||
The `--release` flag performs various optimizations, producing a
|
||
faster executable.
|
||
|
||
After compilation, the executable `scryer-prolog` is available in the
|
||
directory `target/release` and can be invoked to run the system.
|
||
|
||
### Via `cargo install`
|
||
|
||
#### From git
|
||
|
||
```
|
||
cargo install --locked --git https://github.com/mthom/scryer-prolog.git
|
||
```
|
||
|
||
Afterwards the `scryer-prolog` binary will be in the `$HOME/.cargo/bin` directory which is usually added to your PATH
|
||
during the installation of the rust toolchain.
|
||
|
||
#### From Crates.io [](https://crates.io/crates/scryer-prolog) 
|
||
|
||
> [!NOTE]
|
||
> The lates crates.io release can be significantly behind the version available in the git repository
|
||
> The crates.io badge in this sections title is a link to the crates.io page.
|
||
> The msrv badge in the section title referece to the minimum rust toolchain version required to compile the latest crates.io release
|
||
|
||
`scryer-prolog` is also release on crates.io and can be installed with
|
||
|
||
```
|
||
cargo install --locked scryer-prolog
|
||
```
|
||
|
||
### Caveats for Windows
|
||
|
||
On Windows, Scryer Prolog is easier to build inside a [MSYS2](https://www.msys2.org/)
|
||
environment as some crates may require native C compilation. However,
|
||
the resulting binary does not need MSYS2 to run. When executing Scryer in a shell, it is recommended to use a more advanced shell than mintty (the default MSYS2 shell). The [Windows Terminal](https://github.com/microsoft/terminal) works correctly.
|
||
|
||
To build a Windows Installer, you'll need first Scryer Prolog compiled in release mode, then, with WiX Toolset installed, execute:
|
||
```
|
||
candle.exe scryer-prolog.wxs
|
||
light.exe scryer-prolog.wixobj
|
||
```
|
||
It will generate a very basic MSI file which installs the main executable and a shortcut in the Start Menu. It can be installed with a double-click. To uninstall, go to the Control Panel and uninstall as usual.
|
||
|
||
### Building WebAssembly
|
||
|
||
Scryer Prolog has basic WebAssembly support. You can follow `wasm-pack`'s [official instructions](https://rustwasm.github.io/docs/wasm-pack/quickstart.html) to install `wasm-pack` and build it in any way you like.
|
||
|
||
However, none of the [default features](https://doc.rust-lang.org/cargo/reference/features.html#the-default-feature) are currently supported. The preferred way of disabling them is passing [extra options](https://rustwasm.github.io/wasm-pack/book/commands/build.html#extra-options) to `wasm-pack`.
|
||
|
||
For example, if you want a minimal working package without using any bundler like `webpack`, you can do this:
|
||
```
|
||
wasm-pack build --target web -- --no-default-features
|
||
```
|
||
Then a `pkg` directory will be created, containing everything you need for a webapp. You can test whether the package is successfully built by creating an html file, adapted from `wasm-bindgen`'s [official example](https://rustwasm.github.io/wasm-bindgen/examples/without-a-bundler.html) like this:
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html>
|
||
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<title>Scryer Prolog - Sudoku Solver Example</title>
|
||
<script type="module">
|
||
import initScryer, { MachineBuilder } from "./pkg/scryer_prolog.js";
|
||
|
||
// Initialize Scryer Prolog with WASM
|
||
const wasm = await fetch("./pkg/scryer_prolog_bg.wasm");
|
||
const module = await WebAssembly.compile(await wasm.arrayBuffer());
|
||
await initScryer(module);
|
||
|
||
// Set up the Prolog machine
|
||
const machine = new MachineBuilder().build();
|
||
|
||
// Knowledge base: Sudoku rules and problem definition
|
||
const kb = `
|
||
:- use_module(library(format)).
|
||
:- use_module(library(clpz)).
|
||
:- use_module(library(lists)).
|
||
|
||
sudoku(Rows) :-
|
||
length(Rows, 9), maplist(same_length(Rows), Rows),
|
||
append(Rows, Vs), Vs ins 1..9,
|
||
maplist(all_distinct, Rows),
|
||
transpose(Rows, Columns),
|
||
maplist(all_distinct, Columns),
|
||
Rows = [A,B,C,D,E,F,G,H,I],
|
||
blocks(A, B, C),
|
||
blocks(D, E, F),
|
||
blocks(G, H, I).
|
||
|
||
blocks([], [], []).
|
||
blocks([A,B,C|T1], [D,E,F|T2], [G,H,I|T3]) :-
|
||
all_distinct([A,B,C,D,E,F,G,H,I]),
|
||
blocks(T1, T2, T3).
|
||
|
||
problem(1, [[_,_,_,_,_,_,_,_,_],
|
||
[_,_,_,_,_,3,_,8,5],
|
||
[_,_,1,_,2,_,_,_,_],
|
||
[_,_,_,5,_,7,_,_,_],
|
||
[_,_,4,_,_,_,1,_,_],
|
||
[_,9,_,_,_,_,_,_,_],
|
||
[5,_,_,_,_,_,_,7,3],
|
||
[_,_,2,_,1,_,_,_,_],
|
||
[_,_,_,_,4,_,_,_,9]]).
|
||
`;
|
||
|
||
machine.consultModuleString("user", kb);
|
||
|
||
// Run the query
|
||
const query = "problem(1, Rows), sudoku(Rows), maplist(portray_clause, Rows).";
|
||
const answers = machine.runQuery(query);
|
||
|
||
const formattedSolutions = [];
|
||
|
||
// Format the answers
|
||
for (const solution of answers) {
|
||
const rows = solution.bindings["Rows"].list;
|
||
|
||
const grid = rows.map(row =>
|
||
row.list.map(cell => cell.integer)
|
||
);
|
||
|
||
const formatted = grid.map(row => `[${row.join(", ")}]`).join("\n");
|
||
formattedSolutions.push(formatted);
|
||
}
|
||
|
||
// Output results
|
||
const solutionDiv = document.querySelector("#soduku-solution");
|
||
for (const solution of formattedSolutions) {
|
||
const newPre = document.createElement("pre");
|
||
newPre.textContent = solution;
|
||
solutionDiv.appendChild(newPre);
|
||
}
|
||
</script>
|
||
</head>
|
||
|
||
<body>
|
||
<p>Sudoku solver returns:</p>
|
||
<div id="soduku-solution">
|
||
|
||
</div>
|
||
</body>
|
||
|
||
</html>
|
||
```
|
||
|
||
Then you can serve it with your favorite http server like `python -m http.server` or `npx serve`, and access the page with your browser.
|
||
|
||
### Docker Install
|
||
|
||
Pre-built [Docker images are available on Docker Hub](https://hub.docker.com/r/mjt128/scryer-prolog/tags).
|
||
The `latest` tag reflects the state on `master`, which might be unstable.
|
||
There are also tags for Scryer releases 0.9.2 and up.
|
||
Note though, that the base images are not kept up to date at the moment,
|
||
so be wary of security vulnerabilities (see [#2646](https://github.com/mthom/scryer-prolog/issues/2646)).
|
||
|
||
First, install [Docker](https://docs.docker.com/get-docker/) on Linux,
|
||
Windows, or Mac.
|
||
|
||
Once Docker is installed, you can download and run Scryer Prolog with a single
|
||
command:
|
||
```
|
||
$> docker run -it mjt128/scryer-prolog
|
||
```
|
||
|
||
To consult your Prolog files, bind mount your programs folder as a
|
||
[Docker volume](https://docs.docker.com/storage/volumes/):
|
||
|
||
```
|
||
$> docker run -v /home/user/prolog:/mnt -it mjt128/scryer-prolog
|
||
?- consult('/mnt/program.pl').
|
||
true.
|
||
```
|
||
|
||
This works on Windows too:
|
||
|
||
```
|
||
$> docker run -v C:\Users\user\Documents\prolog:/mnt -it mjt128/scryer-prolog
|
||
?- consult('/mnt/program.pl').
|
||
true.
|
||
```
|
||
|
||
## Tutorial
|
||
|
||
Prolog files are loaded by specifying them as arguments on the command
|
||
line. For example, to load `program.pl`, use:
|
||
|
||
```
|
||
$> scryer-prolog program.pl
|
||
```
|
||
|
||
Loading a Prolog file is also called “consulting” it. The built-in
|
||
predicate `consult/1` can be used to consult a file from within
|
||
Prolog:
|
||
|
||
```
|
||
?- consult('program.pl').
|
||
```
|
||
|
||
As an abbreviation for `consult/1`, you can specify a *list* of
|
||
program files, given as *atoms*:
|
||
|
||
```
|
||
?- ['program.pl'].
|
||
```
|
||
|
||
The special notation `[user]` is used to read Prolog text from
|
||
standard input. For example,
|
||
|
||
```
|
||
?- [user].
|
||
hello(declarative_world).
|
||
hello(pure_world).
|
||
```
|
||
|
||
Pressing `RETURN` followed by `Ctrl-d` stops reading from
|
||
standard input and consults the entered Prolog text.
|
||
|
||
After a program is consulted, you can ask *queries* about the
|
||
predicates it defines. For example, with the program shown above:
|
||
|
||
```
|
||
?- hello(What).
|
||
What = declarative_world
|
||
; What = pure_world.
|
||
```
|
||
|
||
Press `SPACE` to show further answers, if any exist. Press `RETURN`
|
||
or `.` to abort the search and return to the
|
||
toplevel prompt. Press `f` to see up to the next multiple of
|
||
5 answers, and `a` to see all answers. Press `h` to show a help
|
||
message.
|
||
|
||
Use `TAB` to complete atoms and predicate names in queries. For
|
||
instance, after consulting the program above, typing `decl` followed
|
||
by `TAB` yields `declarative_world`. Press `TAB` repeatedly
|
||
to cycle through alternative completions.
|
||
|
||
To quit Scryer Prolog, use the standard predicate `halt/0`:
|
||
|
||
```
|
||
?- halt.
|
||
```
|
||
|
||
### Starting Scryer Prolog
|
||
|
||
Scryer Prolog can be started from the command line by specifying
|
||
options, files and additional arguments. All components are optional:
|
||
|
||
<pre>
|
||
scryer-prolog [OPTIONS] [FILES] [-- ARGUMENTS]
|
||
</pre>
|
||
|
||
The supported options are:
|
||
|
||
```
|
||
-h, --help Display help message
|
||
-v, --version Print version information and exit
|
||
-g, --goal GOAL Run the query GOAL after consulting files
|
||
-f Fast startup. Do not load initialization file (~/.scryerrc)
|
||
--no-add-history Prevent adding input to history file (~/.scryer_history)
|
||
```
|
||
|
||
All specified Prolog files are consulted.
|
||
|
||
After Prolog files, application-specific arguments can be specified on
|
||
the command line. These arguments can be accessed from within Prolog
|
||
applications with the predicate `argv/1`, which yields the list
|
||
of arguments represented as strings.
|
||
|
||
Prolog files can also be turned into *shell scripts* as explained in
|
||
https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993.
|
||
|
||
### Dynamic operators
|
||
|
||
Scryer supports dynamic operators. Using the built-in
|
||
arithmetic operators with the usual precedences,
|
||
|
||
```
|
||
?- write_canonical(-5 + 3 - (2 * 4) // 8), nl.
|
||
-(+(-5,3),//(*(2,4),8))
|
||
true.
|
||
```
|
||
|
||
New operators can be defined using the `op` declaration.
|
||
|
||
### First instantiated argument indexing
|
||
|
||
Scryer Prolog indexes on the leftmost argument that is not a variable
|
||
in all clauses of a predicate's definition. We call this strategy
|
||
first *instantiated* argument indexing.
|
||
|
||
A key motivation for first instantiated argument indexing is to enable
|
||
indexing for meta-predicates such as `maplist/N` and `foldl/N`, whose
|
||
first argument is a partial goal that is a variable in the definition
|
||
of these predicates and therefore cannot be used for indexing.
|
||
|
||
For example, a natural definition of `maplist/2` reads:
|
||
|
||
```
|
||
maplist(_, []).
|
||
maplist(Goal_1, [L|Ls]) :-
|
||
call(Goal_1, L),
|
||
maplist(Goal_1, Ls).
|
||
```
|
||
|
||
In this case, first instantiated argument indexing automatically uses
|
||
the *second* argument for indexing, and thus prevents choicepoints for
|
||
calls with lists of fixed lengths (and deterministic goals).
|
||
Conveniently, no auxiliary predicates with reordered arguments are
|
||
needed to benefit from indexing in such cases.
|
||
|
||
Conventional first argument indexing naturally arises as a
|
||
special case of this strategy, if the first argument is instantiated
|
||
in any clause of a predicate's definition.
|
||
|
||
### Strings and partial strings
|
||
|
||
A very compact internal representation of *strings* is one of the key
|
||
innovations of Scryer Prolog. This means that terms which appear as
|
||
lists of characters to Prolog programs are stored in packed
|
||
UTF-8 encoding by the engine.
|
||
|
||
Without this innovation, storing a list of characters in memory would
|
||
use one WAM memory cell per character, one cell per list
|
||
constructor, and one cell for each tail that occurs in the list. Since
|
||
one cell takes 8 bytes in the WAM as implemented by
|
||
Scryer Prolog, the packed representation yields an up to
|
||
**24-fold reduction** of memory usage, and corresponding
|
||
reduction of memory accesses when creating and processing
|
||
strings.
|
||
|
||
Scryer Prolog's compact internal string representation makes it
|
||
ideally suited for the use case Prolog was originally developed for:
|
||
efficient and convenient text processing, especially with definite
|
||
clause grammars (DCGs) as provided by
|
||
[`library(dcgs)`](src/lib/dcgs.pl) and
|
||
[`library(pio)`](src/lib/pio.pl) to transparently apply DCGs to files.
|
||
|
||
In Scryer Prolog, the default value of the Prolog flag `double_quotes`
|
||
is `chars`, which is also the recommended setting. This means that
|
||
lists of characters can be written as double-quoted strings, in the
|
||
tradition of Marseille Prolog.
|
||
|
||
For example, the following query succeeds:
|
||
|
||
```
|
||
?- "abc" = [a,b,c].
|
||
true.
|
||
```
|
||
|
||
This shows that the string `"abc"`, which is represented as a sequence
|
||
of 3 bytes internally, appears to Prolog programs as a list of
|
||
characters.
|
||
|
||
Scryer Prolog uses the same efficient encoding for *partial* strings,
|
||
which appear to Prolog code as partial lists of characters. The
|
||
predicate `partial_string/3` from `library(iso_ext)` lets you
|
||
construct partial strings explicitly. For example:
|
||
|
||
```
|
||
?- partial_string("abc", Ls0, Ls).
|
||
Ls0 = [a,b,c|Ls].
|
||
```
|
||
|
||
In this case, and as the answer illustrates, `Ls0` is
|
||
indistinguishable from a partial list with tail `Ls`, while
|
||
the efficient packed representation is used internally.
|
||
|
||
An important design goal of Scryer Prolog is to *automatically* use
|
||
the efficient string representation whenever possible. Therefore, it
|
||
is only very rarely necessary to use `partial_string/3` explicitly. In
|
||
the above example, posting <tt>Ls0 = [a,b,c|Ls]</tt> yields
|
||
the exact same internal representation, and has the advantage that
|
||
only the standard predicate `(=)/2` is used.
|
||
|
||
The efficient internal representation of strings and partial strings
|
||
was first proposed and explained by Ulrich Neumerkel in
|
||
issues [#24](https://github.com/mthom/scryer-prolog/issues/24)
|
||
and [#95](https://github.com/mthom/scryer-prolog/issues/95), and
|
||
Scryer Prolog is the first Prolog system that implements it.
|
||
|
||
### Occurs check and cyclic terms
|
||
|
||
The *occurs check* is an element of algorithms that perform
|
||
syntactic unification, causing the unification to fail if a variable
|
||
is unified with a term that contains that variable as a proper
|
||
subterm. For efficiency, the *occurs check* is omitted by default
|
||
in Scryer Prolog and many other Prolog systems.
|
||
|
||
In Scryer Prolog, unifications which succeed only if the
|
||
*occurs check* is omitted yield *cyclic terms*, also called
|
||
*rational trees*. For example:
|
||
|
||
```
|
||
?- X = f(X), Y = g(X,Y).
|
||
X = f(X), Y = g(f(X),Y).
|
||
```
|
||
|
||
The creation of cyclic terms often indicates a programming mistake in
|
||
the formulation of Prolog predicates, and to obtain logically sound
|
||
results it is desirable to either perform all unifications with
|
||
*occurs check* enabled, or let Prolog throw an error if enabling
|
||
the *occurs check* is necessary to prevent a unification.
|
||
|
||
Scryer Prolog supports this via the Prolog flag `occurs_check`. It can
|
||
be set to one of the following values to obtain the desired behaviour:
|
||
|
||
- `false`
|
||
Do not perform the *occurs check*. This is the default.
|
||
- `true`
|
||
Perform all unifications with the *occurs check* enabled.
|
||
- `error`
|
||
Yield an error if a unification is performed that the
|
||
*occurs check* would have prevented.
|
||
|
||
Especially when starting with Prolog, we recommend to add the
|
||
following directive to the `~/.scryerrc` configuration file so that
|
||
programming mistakes in predicates that lead to the creation of cyclic
|
||
terms are indicated by errors:
|
||
|
||
```
|
||
:- set_prolog_flag(occurs_check, error).
|
||
```
|
||
|
||
Scryer Prolog implements specialized reasoning to make unifications
|
||
fast in many frequently occurring situations also if the
|
||
*occurs check* is enabled.
|
||
|
||
### Tabling (SLG resolution)
|
||
|
||
One of the foremost attractions of Prolog is that logical consequences
|
||
of pure programs can be derived by various execution strategies
|
||
that differ regarding essential properties such as termination,
|
||
completeness and efficiency.
|
||
|
||
The default execution strategy of Prolog is depth-first search with
|
||
chronological backtracking. This strategy is very efficient. Its main
|
||
drawback is that it is *incomplete*: It may fail to find any solution
|
||
even if one exists.
|
||
|
||
Scryer Prolog supports an alternative execution strategy which is
|
||
called *tabling* and also known as tabled execution and
|
||
SLG resolution. To enable tabled execution for a predicate, use
|
||
[`library(tabling)`](src/lib/tabling.pl) and add a `(table)/1`
|
||
directive for the desired predicate indicator. For example, if we
|
||
write:
|
||
|
||
```
|
||
:- use_module(library(tabling)).
|
||
:- table a/0.
|
||
|
||
a :- a.
|
||
```
|
||
|
||
Then the query `?- a.` *terminates* (and fails), whereas it
|
||
does not terminate with the default execution strategy.
|
||
|
||
Scryer Prolog implements tabling via *delimited continuations* as
|
||
described in [*Tabling as a Library with Delimited
|
||
Control*](https://biblio.ugent.be/publication/6880648/file/6885145.pdf)
|
||
by Desouter et. al.
|
||
|
||
### Constraint Logic Programming (CLP)
|
||
|
||
Scryer Prolog provides excellent support for Constraint Logic
|
||
Programming (CLP), which is the amalgamation of
|
||
Logic Programming (LP) and Constraints.
|
||
|
||
In addition to built-in support for [`dif/2`](src/lib/dif.pl),
|
||
[`freeze/2`](src/lib/freeze.pl),
|
||
[CLP(B)](src/lib/clpb.pl) and [CLP(ℤ)](src/lib/clpz.pl),
|
||
Scryer provides a convenient way to implement new user-defined
|
||
constraints: *Attributed variables* are available via
|
||
[`library(atts)`](src/lib/atts.pl) as in SICStus Prolog,
|
||
which is one of the most sophisticated and fastest constraint systems
|
||
in existence. In [`library(iso_ext)`](src/lib/iso_ext.pl),
|
||
Scryer provides predicates for backtrackable (`bb_b_put/2`) and
|
||
non-backtrackable (`bb_put/2`) global variables, which are needed to
|
||
implement certain types of constraint solvers.
|
||
|
||
These features make Scryer Prolog an ideal platform for teaching,
|
||
learning and developing portable CLP applications.
|
||
|
||
### Modules
|
||
|
||
Scryer has a simple predicate-based module system. It provides a
|
||
way to separate units of code into distinct namespaces, for both
|
||
predicates and operators. See the files
|
||
[`src/lib/*.pl`](src/lib) for
|
||
examples.
|
||
|
||
At the time of this writing, many predicates reside in their own
|
||
modules that need to be imported before they can be used.
|
||
The modules that ship with Scryer Prolog are also called
|
||
*library* modules or *libraries*, and include:
|
||
|
||
* [`lists`](src/lib/lists.pl)
|
||
providing `length/2`, `member/2`, `select/3`, `append/[2,3]`,
|
||
`foldl/[4,5]`, `maplist/[2-9]`, `same_length/2`, `transpose/2` etc.
|
||
* [`dcgs`](src/lib/dcgs.pl)
|
||
Definite Clause Grammars (DCGs), a built-in grammar mechanism
|
||
that uses the operator `(-->)/2` to define grammar rules,
|
||
and the predicates `phrase/[2,3]` to invoke them.
|
||
* [`dif`](src/lib/dif.pl)
|
||
The predicate `dif/2` provides declarative disequality:
|
||
It is true if and only if its arguments are different, and
|
||
delays the test until a sound decision can be made.
|
||
* [`reif`](src/lib/reif.pl)
|
||
providing `if_/3`, `tfilter/3` and related predicates
|
||
as described in *Indexing dif/2*.
|
||
* [`clpz`](src/lib/clpz.pl)
|
||
CLP(ℤ): Constraint Logic Programming over Integers,
|
||
providing declarative integer arithmetic via `(#=)/2`, `(#\=)/2`,
|
||
`(#>=)/2` etc., and various global constraints and
|
||
enumeration predicates for solving combinatorial tasks.
|
||
* [`pairs`](src/lib/pairs.pl)
|
||
By convention, *pairs* are Prolog terms with
|
||
principal functor `(-)/2`, written as `Key-Value`.
|
||
This library provides `pairs_keys_values/3`,
|
||
`pairs_keys/2`, and other predicates to reason about pairs.
|
||
* [`si`](src/lib/si.pl)
|
||
The predicates `atom_si/1`, `integer_si/1`, `atomic_si/1`
|
||
and `list_si/1` implement sound type checks. They raise
|
||
instantiation errors if no decision can be made.
|
||
They are declarative replacements for logically flawed
|
||
lower-level type tests. For instance, instead of `integer(X)`,
|
||
write `integer_si(X)` to ensure soundness of your programs.
|
||
"si" stands for *sufficiently instantiated*, and also for
|
||
*sound inference*.
|
||
* [`debug`](src/lib/debug.pl)
|
||
Various predicates that allow for declarative debugging.
|
||
* [`pio`](src/lib/pio.pl)
|
||
`phrase_from_file/2` applies a DCG nonterminal to the contents of a
|
||
file, reading lazily only as much as is needed. Due to the compact
|
||
internal string representation, also extremely large files can be
|
||
efficiently processed with Scryer Prolog in this way.
|
||
`phrase_to_file/2` and `phrase_to_stream/2` write lists of
|
||
characters described by DCGs to files and streams, respectively.
|
||
* [`lambda`](src/lib/lambda.pl)
|
||
Lambda expressions to simplify higher order programming.
|
||
* [`charsio`](src/lib/charsio.pl) Various predicates that are useful
|
||
for parsing and reasoning about characters, notably `char_type/2` to
|
||
classify characters according to their type, and conversion
|
||
predicates for different encodings of strings.
|
||
* [`error`](src/lib/error.pl)
|
||
`must_be/2` and `can_be/2` complement the type checks provided by
|
||
[`library(si)`](src/lib/si.pl), and are especially useful for
|
||
Prolog library authors.
|
||
* [`tabling`](src/lib/tabling.pl)
|
||
The operator `(table)/1` is used in directives that prepare
|
||
predicates for tabled execution (SLG resolution).
|
||
* [`format`](src/lib/format.pl)
|
||
The nonterminal `format_//2` is used to describe formatted output,
|
||
arranging arguments according to a given format string.
|
||
The predicates `format/[2,3]`, `portray_clause/[1,2]` and `listing/1`
|
||
provide formatted *impure* output.
|
||
* [`assoc`](src/lib/assoc.pl)
|
||
providing `empty_assoc/1`, `get_assoc/3`, `put_assoc/4` etc.
|
||
to manage elements in AVL trees which ensure
|
||
*O*(log(*N*)) access.
|
||
* [`ordsets`](src/lib/ordsets.pl)
|
||
represents ordered sets as lists.
|
||
* [`clpb`](src/lib/clpb.pl)
|
||
CLP(B): Constraint Logic Programming over Boolean variables,
|
||
a BDD-based SAT solver provided via the predicates
|
||
`sat/1`, `taut/2`, `labeling/1` etc.
|
||
* [`arithmetic`](src/lib/arithmetic.pl)
|
||
Arithmetic predicates such as `lsb/2`, `msb/2` and
|
||
`number_to_rational/2`.
|
||
* [`time`](src/lib/time.pl) Predicates for reasoning about
|
||
time, including `time/1` to measure the CPU time of a goal,
|
||
`current_time/1` to obtain the current system time, the nonterminal
|
||
`format_time//2` to describe strings with dates and times, and
|
||
`sleep/1` to slow down a computation.
|
||
* [`files`](src/lib/files.pl)
|
||
Predicates for reasoning about files and directories, such as
|
||
`directory_files/2`, `file_exists/1` and `file_size/2`.
|
||
* [`cont`](src/lib/cont.pl)
|
||
Provides *delimited continuations* via `reset/3` and `shift/1`.
|
||
* [`random`](src/lib/random.pl)
|
||
Probabilistic predicates and random number generators.
|
||
* [`http/http_open`](src/lib/http/http_open.pl) Open a stream to
|
||
read answers from web servers. HTTPS is also supported.
|
||
* [`http/http_server`](src/lib/http/http_server.pl) Runs a HTTP/1.1 and HTTP/2.0 web server. Uses [Warp](https://github.com/seanmonstar/warp) as a backend. Supports some query and form handling.
|
||
* [`sgml`](src/lib/sgml.pl)
|
||
`load_html/3` and `load_xml/3` represent HTML and XML documents
|
||
as Prolog terms for convenient and efficient reasoning. Use
|
||
[`library(xpath)`](src/lib/iso_ext.pl) to extract information from
|
||
parsed documents.
|
||
* [`csv`](src/lib/csv.pl)
|
||
`parse_csv//1` and `parse_csv//2` can be used with [`phrase_from_file/2`](src/lib/pio.pl)
|
||
or [`phrase/2`](src/lib/dcgs.pl) to parse csv
|
||
* [`serialization/abnf`](src/lib/serialization/abnf.pl)
|
||
DCGs describing the
|
||
[ABNF grammar core (RFC 5234)](https://tools.ietf.org/html/rfc5234#appendix-B.1),
|
||
which is used to describe many [IETF](https://www.ietf.org/standards/rfcs/)
|
||
syntaxes, such as [HTTP v1.1](https://www.rfc-editor.org/rfc/rfc7230.html#page-82),
|
||
[SMTP](https://www.rfc-editor.org/rfc/rfc5321.html),
|
||
[iCalendar](https://www.rfc-editor.org/rfc/rfc5545.html), and more.
|
||
* [`serialization/json`](src/lib/serialization/json.pl)
|
||
`json_chars//1` can be used with [`phrase_from_file/2`](src/lib/pio.pl)
|
||
or [`phrase/2`](src/lib/dcgs.pl) to parse and generate
|
||
[JSON](https://www.json.org/json-en.html).
|
||
* [`xpath`](src/lib/xpath.pl)
|
||
The predicate `xpath/3` is used for convenient reasoning about HTML
|
||
and XML documents, inspired by the XPath language. This library
|
||
is often used together with [`library(sgml)`](src/lib/sgml.pl).
|
||
* [`sockets`](src/lib/sockets.pl)
|
||
Predicates for opening and accepting TCP connections as streams.
|
||
* [`os`](src/lib/os.pl)
|
||
Predicates for reasoning about environment variables.
|
||
* [`iso_ext`](src/lib/iso_ext.pl)
|
||
Conforming extensions to and candidates for inclusion in the Prolog
|
||
ISO standard, such as `setup_call_cleanup/3`, `call_nth/2` and
|
||
`call_with_inference_limit/3`.
|
||
* [`crypto`](src/lib/crypto.pl)
|
||
Cryptographically secure random numbers and hashes, HMAC-based key
|
||
derivation (HKDF), password-based key derivation (PBKDF2),
|
||
public key signatures and signature verification with Ed25519,
|
||
ECDH key exchange over Curve25519 (X25519), authenticated symmetric
|
||
encryption with ChaCha20-Poly1305, and reasoning about elliptic curves.
|
||
* [`uuid`](src/lib/uuid.pl) UUIDv4 generation and hex representation
|
||
* [`tls`](src/lib/tls.pl)
|
||
Predicates for negotiating TLS connections explicitly.
|
||
* [`ugraphs`](src/lib/ugraphs.pl) Graph manipulation library
|
||
* [`simplex`](src/lib/simplex.pl) Providing `assignment/2`,
|
||
`transportation/4` and other predicates for solving linear
|
||
programming problems.
|
||
|
||
To use predicates provided by the `lists` library, write:
|
||
|
||
```
|
||
?- use_module(library(lists)).
|
||
```
|
||
|
||
To load modules contained in files, the `library` functor can be
|
||
omitted, prompting Scryer to search for the file (specified as an
|
||
atom) from its working directory:
|
||
|
||
```
|
||
?- use_module('file.pl').
|
||
```
|
||
|
||
`use_module` directives can be qualified by adding a list of imports:
|
||
|
||
```
|
||
?- use_module(library(lists), [member/2]).
|
||
```
|
||
|
||
A qualified `use_module` can be used to remove imports from the
|
||
toplevel by calling it with an empty import list.
|
||
|
||
The `(:)/2` operator resolves calls to predicates that might not be
|
||
imported to the current working namespace:
|
||
|
||
```
|
||
?- lists:member(X, Xs).
|
||
```
|
||
|
||
The [user] prompt can also be used to define modules inline at the
|
||
REPL:
|
||
|
||
```
|
||
?- [user].
|
||
:- module(test, [local_member/2]).
|
||
:- use_module(library(lists)).
|
||
|
||
local_member(X, Xs) :- member(X, Xs).
|
||
```
|
||
|
||
The user listing can also be terminated by placing `end_of_file.` at
|
||
the end of the stream.
|
||
|
||
### Configuration file
|
||
|
||
At startup, Scryer Prolog consults the file `~/.scryerrc`, if the file
|
||
exists. This file is useful to automatically load libraries and define
|
||
predicates that you need often.
|
||
|
||
For example, a sensible starting point for `~/.scryerrc` is:
|
||
|
||
```
|
||
:- use_module(library(lists)).
|
||
:- use_module(library(dcgs)).
|
||
:- use_module(library(reif)).
|
||
```
|
||
|
||
### Development environment
|
||
|
||
To write and edit Prolog programs, we recommend
|
||
[GNU Emacs](https://www.gnu.org/software/emacs/) with the
|
||
[Prolog mode](https://bruda.ca/emacs/prolog_mode_for_emacs)
|
||
maintained by Stefan Bruda.
|
||
|
||
Use [ediprolog](https://www.metalevel.at/ediprolog/) to consult
|
||
Prolog code and evaluate Prolog queries in arbitrary
|
||
Emacs buffers.
|
||
|
||
Emacs definitions that show Prolog terms as trees are available
|
||
in [tools](tools).
|
||
|
||
To *debug* Prolog code, we recommend the predicates from
|
||
[**`library(debug)`**](src/lib/debug.pl), most notably:
|
||
|
||
- `(*)/1` to *"generalize away"* a Prolog goal. Use it to debug
|
||
unexpected failures by generalizing your definitions until they
|
||
succeed. Simply place `*` in front of a goal to generalize it away.
|
||
- `($)/1` to emit a *trace* of the execution, showing when a goal
|
||
is invoked, and when it has succeeded. Place `$` in front of a goal
|
||
to emit this information for that goal.
|
||
|
||
This way of debugging Prolog code has several major benefits, such as:
|
||
It stays close to the actual Prolog code under consideration, it does
|
||
not need additional tools and formalisms for its application, and
|
||
further, it encourages declarative reasoning that can in principle
|
||
also be performed automatically.
|
||
|
||
## Applications
|
||
|
||
Scryer Prolog's strong commitment to the Prolog ISO standard makes it
|
||
ideally suited for use in corporations and government agencies
|
||
that are subject to strict regulations pertaining to interoperability,
|
||
standards compliance and warranty.
|
||
|
||
Successful existing applications of Scryer Prolog include:
|
||
|
||
- [DocLog](https://github.com/aarroyoc/doclog) which generates
|
||
Scryer's own documentation and homepage
|
||
- [Grants4Companies](https://arxiv.org/abs/2406.15293): reasoning
|
||
about business grants in the Austrian public administration
|
||
- parts of the [precautionary](https://github.com/dcnorris/precautionary/tree/main/exec/prolog)
|
||
package for the analysis of dose-escalation trials in the
|
||
safety-critical and highly regulated domain of oncology
|
||
trial design, described in [*An Executable Specification of
|
||
Oncology Dose-Escalation Protocols with Prolog*](https://arxiv.org/abs/2402.08334)
|
||
- semantic reasoning and queries in [AD4M](https://github.com/coasys/ad4m),
|
||
an agent-centric distributed application meta-ontology.
|
||
|
||
Scryer Prolog is also very well suited for teaching and learning
|
||
Prolog, and for testing syntactic conformance and hence portability of
|
||
existing Prolog programs.
|
||
|
||
## Scryer Prolog Meetups
|
||
|
||
Scryer Prolog Meetups are an excellent opportunity to present and get
|
||
to know the latest developments in Scryer Prolog and its
|
||
applications, to exchange ideas about current plans and
|
||
future directions, and to discuss projects and visions
|
||
in person.
|
||
|
||
- [Scryer Prolog Meetup 2023](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2023/)
|
||
in Düsseldorf, Germany. Its [announcement](https://github.com/mthom/scryer-prolog/discussions/1813)
|
||
and [discussion](https://github.com/mthom/scryer-prolog/discussions/2160).
|
||
- [Scryer Prolog Meetup 2024](https://www.digitalaustria.gv.at/wissenswertes/events/scryerprologmeetup2024)
|
||
in Vienna, Austria. Its [announcement and discussion](https://github.com/mthom/scryer-prolog/discussions/2377).
|
||
- **Save the date:** The [Scryer Prolog Meetup 2025](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2025/)
|
||
will take place on Nov. 13th and 14th 2025 in Düsseldorf, Germany.
|
||
Its [announcement](https://github.com/mthom/scryer-prolog/discussions/2948).
|
||
|
||
## Support and discussions
|
||
|
||
If Scryer Prolog crashes or yields unexpected errors, consider filing
|
||
an [issue](https://github.com/mthom/scryer-prolog/issues).
|
||
|
||
To get in touch with the Scryer Prolog community, participate in
|
||
[discussions](https://github.com/mthom/scryer-prolog/discussions)
|
||
or visit our #scryer IRC channel on [Libera](https://libera.chat)!
|