deriva 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/conformance-report.md +1 -1
- package/docs/guide.md +22 -19
- package/docs/language-reference.md +89 -41
- package/package.json +1 -1
- package/src/builtins/arithmetic.js +2 -2
- package/src/builtins/terms.js +1 -1
- package/src/cli.js +1 -1
- package/src/explain.js +2 -2
- package/src/parser.js +1 -1
- package/src/solver.js +2 -2
- package/test/conformance/README.md +3 -3
- package/test/run-conformance-report.mjs +1 -1
- package/test/run-regression.mjs +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Deriva
|
|
2
2
|
|
|
3
3
|
<p align="left">
|
|
4
|
-
<img src="docs/assets/deriva-logo.png" alt="Deriva logo" width="
|
|
4
|
+
<img src="docs/assets/deriva-logo.png" alt="Deriva logo" width="70">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/deriva)
|
package/conformance-report.md
CHANGED
package/docs/guide.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This guide introduces Deriva, a small Horn-clause language and engine whose source syntax is Prolog-like but deliberately its own compact language for facts, rules, goals, answers, and proofs. Deriva works over ordinary terms, lists, arithmetic, strings, and finite search. Run it with the `deriva` CLI, or use `node bin/deriva.js` when working directly from a source checkout.
|
|
4
4
|
|
|
5
|
+
This documentation uses **Deriva** for the project, language, and engine, and
|
|
6
|
+
`deriva` for the npm package and CLI command.
|
|
7
|
+
|
|
5
8
|
Programs write relations directly, for example `ancestor(pat, emma)` or `status(case1, accepted)`. Web identifiers can be written as ordinary quoted atoms that include angle brackets, for example `'<https://schema.org/name>'`, when a program needs explicit IRI-shaped names without prefix declarations. Deriva output is ordinary Deriva syntax: by default, the CLI materializes selected answer facts and prints those facts only. Pass `--proof` (or `-p`) when you also want each answer followed by a `why/2` explanation fact that records the proof. Programs may add `materialize/2` declarations such as `materialize(answer, 2).` to focus output on selected predicates.
|
|
6
9
|
|
|
7
10
|
Execution is automatically hybrid. Ordinary predicates use indexed
|
|
@@ -15,7 +18,7 @@ For the normative language definition, including lexical syntax, terms, clauses,
|
|
|
15
18
|
## Contents
|
|
16
19
|
|
|
17
20
|
1. [Quick start](#quick-start)
|
|
18
|
-
2. [Running deriva](#running-deriva)
|
|
21
|
+
2. [Running the `deriva` CLI](#running-the-deriva-cli)
|
|
19
22
|
3. [Default output](#default-output)
|
|
20
23
|
4. [Writing programs](#writing-programs)
|
|
21
24
|
5. [Aggregation helpers](#aggregation-helpers)
|
|
@@ -61,7 +64,7 @@ deriva --version
|
|
|
61
64
|
|
|
62
65
|
`npm install -g .` is another local-checkout option if you want npm to install the package globally instead of linking it. Avoid hand-written `/usr/local/bin` symlinks unless you really need one; npm already reads the `bin` entry in `package.json` and creates the correct executable shim.
|
|
63
66
|
|
|
64
|
-
## Running deriva
|
|
67
|
+
## Running the `deriva` CLI
|
|
65
68
|
|
|
66
69
|
The commands in this section use `deriva` for readability. In a source checkout where you have not run `npm link` or `npm install -g .`, replace `deriva` with `node bin/deriva.js`, or run the command through `npm exec --yes --package=. -- deriva`.
|
|
67
70
|
|
|
@@ -72,7 +75,7 @@ deriva --version
|
|
|
72
75
|
deriva -v
|
|
73
76
|
```
|
|
74
77
|
|
|
75
|
-
Run a program and let
|
|
78
|
+
Run a program and let Deriva print derived binary facts:
|
|
76
79
|
|
|
77
80
|
```sh
|
|
78
81
|
deriva examples/ancestor.pl
|
|
@@ -85,7 +88,7 @@ deriva --proof examples/ancestor.pl
|
|
|
85
88
|
deriva -p examples/ancestor.pl
|
|
86
89
|
```
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
Deriva-readable explanations are opt-in proof output. Each `why/2` fact contains a nested abstract proof term, and a blank line separates consecutive explanations. Using Deriva syntax for explanations keeps them in the same language as the answers themselves: they are readable by humans, parseable by Deriva, easy to test, and can be transformed or explained further like any other Deriva data. For example:
|
|
89
92
|
|
|
90
93
|
```deriva
|
|
91
94
|
type(socrates, mortal).
|
|
@@ -106,7 +109,7 @@ why(
|
|
|
106
109
|
|
|
107
110
|
```
|
|
108
111
|
|
|
109
|
-
The explanation output can itself be read as
|
|
112
|
+
The explanation output can itself be read as Deriva input; for example, another program can materialize `why/2` facts such as `why(type(socrates, mortal), Proof)`. `--proof` adds only these explanation facts; it does not change the answers found by the solver.
|
|
110
113
|
|
|
111
114
|
### Explanation cookbook
|
|
112
115
|
|
|
@@ -181,7 +184,7 @@ ancestor(X, Y) :- parent(X, Y).
|
|
|
181
184
|
ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).
|
|
182
185
|
```
|
|
183
186
|
|
|
184
|
-
By default,
|
|
187
|
+
By default, Deriva asks for new ground consequences of selected output predicates, suppresses duplicates, excludes source facts, sorts the result, and prints Prolog facts:
|
|
185
188
|
|
|
186
189
|
```deriva
|
|
187
190
|
ancestor(jan, emma).
|
|
@@ -213,7 +216,7 @@ answer(case1, accepted).
|
|
|
213
216
|
|
|
214
217
|
## Writing programs
|
|
215
218
|
|
|
216
|
-
A good
|
|
219
|
+
A good Deriva program normally has three layers:
|
|
217
220
|
|
|
218
221
|
1. source facts;
|
|
219
222
|
2. helper predicates for calculation or search;
|
|
@@ -306,7 +309,7 @@ best_cycle(Cycle, Cost) :-
|
|
|
306
309
|
|
|
307
310
|
## Context data
|
|
308
311
|
|
|
309
|
-
Comma terms can be data as well as conjunctions.
|
|
312
|
+
Comma terms can be data as well as conjunctions. Deriva provides two context utilities:
|
|
310
313
|
|
|
311
314
|
```deriva
|
|
312
315
|
holds((name(alice, "Alice"), knows(alice, bob)), name(S, O)).
|
|
@@ -513,13 +516,13 @@ for f in examples/proof/*.pl; do
|
|
|
513
516
|
done
|
|
514
517
|
```
|
|
515
518
|
|
|
516
|
-
Run the full
|
|
519
|
+
Run the full Deriva suite:
|
|
517
520
|
|
|
518
521
|
```sh
|
|
519
522
|
npm run test:deriva
|
|
520
523
|
```
|
|
521
524
|
|
|
522
|
-
The
|
|
525
|
+
The Deriva corpus runner runs in this order: Conformance, Regression/API/White-box, Examples. Each section prints its own subtotal, followed by a suite-specific grand total. The suite checks the conformance cases derived from the language reference, supplemental regression/API/white-box checks, and every runnable example against its golden output.
|
|
523
526
|
|
|
524
527
|
Run only one internal suite when you are iterating:
|
|
525
528
|
|
|
@@ -537,7 +540,7 @@ node test/run-conformance-report.mjs
|
|
|
537
540
|
|
|
538
541
|
Release preparation runs the same report and writes [`conformance-report.md`](../conformance-report.md), so each published package carries a current conformance summary.
|
|
539
542
|
|
|
540
|
-
The conformance suite lives in [`test/conformance/`](../test/conformance/) as a file-based
|
|
543
|
+
The conformance suite lives in [`test/conformance/`](../test/conformance/) as a file-based Deriva corpus. Positive cases pair `cases/<name>.pl` with exact expected stdout under `expected/<name>.pl`; negative cases pair `errors/<name>.pl` with exact expected error text under `expected-errors/<name>.txt`; warning cases pair `warnings/<name>.pl` with exact `--warnings` stdout and stderr files under `expected-warnings/`; proof cases pair `proofs/<name>.pl` with exact `--proof` output under `expected-proofs/`. Cases may be grouped in category directories such as `arithmetic/`, `strings/`, `lists/`, `terms/`, `atoms/`, `variables/`, `negation/`, and `syntax/`, so another implementation can reuse the same corpus as an executable language contract. The suite covers the standard language surface from the language reference, including reusable built-ins, standard errors, standard warnings, and the machine-readable `why/2` proof-output contract. The regression suite lives in [`test/run-regression.mjs`](../test/run-regression.mjs) and covers CLI regressions, the public JavaScript API, and white-box invariants for parser, unification, and indexing behavior.
|
|
541
544
|
|
|
542
545
|
## Development and release
|
|
543
546
|
|
|
@@ -570,7 +573,7 @@ The `preversion` script reruns the full test suite and refreshes [`conformance-r
|
|
|
570
573
|
|
|
571
574
|
## Relationship to Eyeling
|
|
572
575
|
|
|
573
|
-
[Eyeling](https://github.com/eyereasoner/eyeling) and
|
|
576
|
+
[Eyeling](https://github.com/eyereasoner/eyeling) and Deriva share the same goal of small, inspectable rule-based reasoning in JavaScript, but they make different language and implementation trade-offs.
|
|
574
577
|
|
|
575
578
|
Eyeling is the RDF/Notation3 member of the family. It reads N3-style triples, quoted formulas, forward rules written with `=>`, backward rules written with `<=`, RDF terms, RDF-JS data, and RDF-oriented streams. That makes it the better fit when data interchange with RDF/N3 tools is the main requirement.
|
|
576
579
|
|
|
@@ -581,13 +584,13 @@ A useful rule of thumb:
|
|
|
581
584
|
| Use case | Prefer | Why |
|
|
582
585
|
| --- | --- | --- |
|
|
583
586
|
| RDF/N3 data, triples, prefixes, graph terms, RDF-JS, RDF message streams | Eyeling | The surface language and APIs are RDF/Notation3-native. |
|
|
584
|
-
| Compact relational rules over ordinary terms, lists, arithmetic, and finite search |
|
|
587
|
+
| Compact relational rules over ordinary terms, lists, arithmetic, and finite search | Deriva | The syntax is shorter for non-RDF relation programs and output is ordinary facts. |
|
|
585
588
|
| Human-auditable derivations | Either | Both can emit proof explanations when requested. |
|
|
586
|
-
| Large generated Horn-clause workloads |
|
|
589
|
+
| Large generated Horn-clause workloads | Deriva | The engine specializes in predicate/arity indexing, scalar argument indexes, fast fact paths, and materialized output goals. |
|
|
587
590
|
|
|
588
|
-
On local smoke benchmarks,
|
|
591
|
+
On local smoke benchmarks, Deriva is substantially faster on large generated Horn-clause and recursion-heavy workloads. These numbers are 5-run medians with stdout redirected to `/dev/null`, using Node.js `v22.16.0`, Deriva from this checkout, and Eyeling package version `1.34.6` with its default output mode. The ratio is `Eyeling median / Deriva median`, so larger numbers mean Deriva was faster.
|
|
589
592
|
|
|
590
|
-
| Example |
|
|
593
|
+
| Example | Deriva median | Eyeling median | Ratio |
|
|
591
594
|
| --- | ---: | ---: | ---: |
|
|
592
595
|
| `fundamental-theorem-arithmetic` | `0.16 sec` | `2.00 sec` | `12.66x` |
|
|
593
596
|
| `deep-taxonomy-100000` | `1.69 sec` | `4.72 sec` | `2.79x` |
|
|
@@ -597,13 +600,13 @@ On local smoke benchmarks, deriva is substantially faster on large generated Hor
|
|
|
597
600
|
|
|
598
601
|
Treat these as smoke comparisons rather than a formal benchmark: hardware, Node.js version, package version, CLI startup, and output mode all matter.
|
|
599
602
|
|
|
600
|
-
The projects are therefore complementary rather than replacements for each other: Eyeling optimizes for Semantic Web interoperability and N3 expressiveness;
|
|
603
|
+
The projects are therefore complementary rather than replacements for each other: Eyeling optimizes for Semantic Web interoperability and N3 expressiveness; Deriva optimizes for a small standard-looking relational rule language and fast finite goal-directed execution.
|
|
601
604
|
|
|
602
605
|
## Performance notes
|
|
603
606
|
|
|
604
607
|
Use `-s` or `--stats` for a quick sanity check while optimizing solver changes. It prints counters such as `solve_goals_calls`, `unify_calls`, `deterministic_rule_expansions`, `candidate_lists_selected`, `clause_candidates_considered`, `clauses_tried`, `max_depth`, and `max_solver_call_depth` to stderr, leaving normal output stable for golden-file tests. The `max_solver_call_depth` counter is especially useful for browser regressions, where the VM call stack can be tighter than a command-line run. Use `-w` or `--warnings` separately when you want portability diagnostics without enabling stricter parsing.
|
|
605
608
|
|
|
606
|
-
|
|
609
|
+
Deriva hashes predicate groups by name and arity, then indexes clauses by scalar argument values. It also builds two-argument composite indexes for scalar pairs and probes those composite indexes without per-lookup heap allocation. This helps both large generated programs with many predicates and selective queries such as:
|
|
607
610
|
|
|
608
611
|
```deriva
|
|
609
612
|
edge(g1, a, X).
|
|
@@ -611,7 +614,7 @@ path(a, Y).
|
|
|
611
614
|
status(Case, accepted).
|
|
612
615
|
```
|
|
613
616
|
|
|
614
|
-
Ground facts use a fast path that avoids freshening and copying a rule body. Recursive-predicate detection uses an explicit work stack, which keeps large predicate chains safer in the browser.
|
|
617
|
+
Ground facts use a fast path that avoids freshening and copying a rule body. Recursive-predicate detection uses an explicit work stack, which keeps large predicate chains safer in the browser. Deriva automatically tables positive recursive groups, including dependencies reached through supported meta-goals and directly materialized relations. Cyclic calls iterate to an answer fixed point before replay. Recursive components containing a negative dependency retain guarded ordinary resolution because positive fixed-point tabling is not a semantics for unstratified negation. The engine also infers common structurally decreasing input positions; calls with an unbound structural input and fully open calls retain ordinary resolution rather than trying to materialize a potentially infinite relation. Authors do not need a search-control declaration.
|
|
615
618
|
|
|
616
619
|
Predicates can also carry advisory mode and determinism declarations for documentation and host tooling:
|
|
617
620
|
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
- [7.2 Failure](#72-failure)
|
|
30
30
|
- [7.3 Finite search expectation](#73-finite-search-expectation)
|
|
31
31
|
- [8. Logical reading: Herbrand semantics](#8-logical-reading-herbrand-semantics)
|
|
32
|
-
- [8.1
|
|
33
|
-
- [8.2
|
|
34
|
-
- [8.3
|
|
35
|
-
- [8.4
|
|
36
|
-
- [8.5
|
|
32
|
+
- [8.1 Why use a Herbrand interpretation?](#81-why-use-a-herbrand-interpretation)
|
|
33
|
+
- [8.2 Variables and quantification](#82-variables-and-quantification)
|
|
34
|
+
- [8.3 Equality, identity, and unification](#83-equality-identity-and-unification)
|
|
35
|
+
- [8.4 Goal-directed execution versus model-theoretic meaning](#84-goal-directed-execution-versus-model-theoretic-meaning)
|
|
36
|
+
- [8.5 Built-ins and operational extensions](#85-built-ins-and-operational-extensions)
|
|
37
|
+
- [8.6 Stratified negation](#86-stratified-negation)
|
|
37
38
|
- [9. Standard built-in predicates](#9-standard-built-in-predicates)
|
|
38
39
|
- [9.1 Equality and unification](#91-equality-and-unification)
|
|
39
40
|
- [9.2 Arithmetic](#92-arithmetic)
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
- [11.1 Automatic hybrid reasoning](#111-automatic-hybrid-reasoning)
|
|
51
52
|
- [11.2 Default-output materialization](#112-default-output-materialization)
|
|
52
53
|
- [11.3 Advisory modes and determinism](#113-advisory-modes-and-determinism)
|
|
53
|
-
- [12.
|
|
54
|
+
- [12. Deriva Sockets](#12-deriva-sockets)
|
|
54
55
|
- [12.1 Socket vocabulary](#121-socket-vocabulary)
|
|
55
56
|
- [12.2 Socket example](#122-socket-example)
|
|
56
57
|
- [12.3 Sockets and AI agents](#123-sockets-and-ai-agents)
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
|
|
68
69
|
## Abstract
|
|
69
70
|
|
|
70
|
-
Deriva is a compact definite-clause language whose surface syntax is Prolog-like term and clause syntax with deliberate
|
|
71
|
+
Deriva is a compact definite-clause language whose surface syntax is Prolog-like term and clause syntax with deliberate Deriva choices for rule-based programs over ordinary terms, lists, arithmetic, strings, and finite search. A Deriva program is a finite sequence of facts and Horn clauses. The underlying declarative semantics of the pure language is **Herbrand semantics**: constants, compound terms, and lists denote themselves, and predicates denote sets of ground atomic formulas over those terms. Evaluation is goal-directed: goals are solved by unification against facts, rules, and a fixed set of built-in predicates.
|
|
71
72
|
|
|
72
73
|
Deriva is intentionally smaller than ISO Prolog. It supports compact Horn-clause reasoning, list processing, arithmetic examples, finite search, and context data, without operators, cut, modules, dynamic predicates, DCGs, zero-arity compound syntax, or a complete ISO standard library.
|
|
73
74
|
|
|
@@ -89,7 +90,7 @@ A **goal** is an atomic formula, a built-in call, or a comma conjunction.
|
|
|
89
90
|
|
|
90
91
|
A **source fact** is a fact written directly in the input program. A **new derivation** is a ground consequence found through at least one rule and not merely repeated from the source facts.
|
|
91
92
|
|
|
92
|
-
The **Herbrand universe** of a program is the set of all ground
|
|
93
|
+
The **Herbrand universe** of a program is the set of all ground Deriva terms constructible from the constants and functors in the program, together with the built-in list constructors `[]` and `./2` where lists are used. The **Herbrand base** is the set of all ground atomic formulas whose predicate symbols occur in the program and whose arguments are terms from the Herbrand universe.
|
|
93
94
|
|
|
94
95
|
## 2. Design goals
|
|
95
96
|
|
|
@@ -329,7 +330,7 @@ pair(3, nested(atom, [x, y]))
|
|
|
329
330
|
|
|
330
331
|
The same concrete syntax is used for atomic formulas when the compound appears as a fact, rule head, or goal. In `parent(pat, jan).`, `parent/2` is a predicate symbol and the whole expression is an atomic formula. In `value(x, parent(pat, jan)).`, the inner `parent(pat, jan)` is ordinary compound data.
|
|
331
332
|
|
|
332
|
-
The functor or predicate name is fixed syntactically and is written as an atom constant.
|
|
333
|
+
The functor or predicate name is fixed syntactically and is written as an atom constant. Deriva does not support variables in predicate or functor position.
|
|
333
334
|
|
|
334
335
|
### 5.4 Lists
|
|
335
336
|
|
|
@@ -375,7 +376,7 @@ Clauses with the same predicate name and arity define one predicate group. Predi
|
|
|
375
376
|
|
|
376
377
|
## 7. Goals and proof search
|
|
377
378
|
|
|
378
|
-
Goals are solved left-to-right. For a user-defined atomic-formula goal,
|
|
379
|
+
Goals are solved left-to-right. For a user-defined atomic-formula goal, Deriva selects candidate clauses by predicate name, arity, and available indexes. A candidate clause is freshened, its head is unified with the goal, and then its body is solved.
|
|
379
380
|
|
|
380
381
|
A conjunction goal succeeds when all conjunct goals succeed in order. An answer is printed as the resolved answer term followed by a period.
|
|
381
382
|
|
|
@@ -385,11 +386,11 @@ Unification follows the ordinary first-order term structure used by the language
|
|
|
385
386
|
|
|
386
387
|
### 7.2 Failure
|
|
387
388
|
|
|
388
|
-
A goal fails when no built-in case or user clause can prove it.
|
|
389
|
+
A goal fails when no built-in case or user clause can prove it. Deriva has no exception term language; parse errors and resource failures are implementation errors reported to the host.
|
|
389
390
|
|
|
390
391
|
### 7.3 Finite search expectation
|
|
391
392
|
|
|
392
|
-
Programs and selected output goals SHOULD be written so the relevant search space is finite.
|
|
393
|
+
Programs and selected output goals SHOULD be written so the relevant search space is finite. Deriva includes recursion guards and tabling support, but it is not required to terminate for arbitrary recursive logic programs.
|
|
393
394
|
|
|
394
395
|
## 8. Logical reading: Herbrand semantics
|
|
395
396
|
|
|
@@ -413,11 +414,58 @@ is read universally over Herbrand terms: for every substitution of `X`, `Y`, and
|
|
|
413
414
|
|
|
414
415
|
Equivalently, the least Herbrand model is obtained by repeatedly applying the immediate-consequence operation: start with the source facts, add every ground rule head whose ground body is already true, and continue to the least fixed point. This definition is mathematical; an implementation does not have to compute the model bottom-up.
|
|
415
416
|
|
|
416
|
-
### 8.1
|
|
417
|
+
### 8.1 Why use a Herbrand interpretation?
|
|
417
418
|
|
|
418
|
-
|
|
419
|
+
Herbrand semantics is not an alternative to model theory: a Herbrand
|
|
420
|
+
interpretation is a particular kind of Tarskian structure. Deriva chooses this
|
|
421
|
+
restricted structure because programs manipulate symbolic terms directly and
|
|
422
|
+
need their identity to be predictable without a separate set of domain axioms.
|
|
419
423
|
|
|
420
|
-
|
|
424
|
+
Consider this program:
|
|
425
|
+
|
|
426
|
+
```deriva
|
|
427
|
+
materialize(different, 2).
|
|
428
|
+
|
|
429
|
+
different(alice, bob) :-
|
|
430
|
+
neq(alice, bob).
|
|
431
|
+
|
|
432
|
+
different(ticket(alice), ticket(bob)) :-
|
|
433
|
+
neq(ticket(alice), ticket(bob)).
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
It produces:
|
|
437
|
+
|
|
438
|
+
```deriva
|
|
439
|
+
different(alice, bob).
|
|
440
|
+
different(ticket(alice), ticket(bob)).
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
In an unrestricted Tarskian interpretation, the constants `alice` and `bob`
|
|
444
|
+
may denote the same domain element unless a distinctness axiom says otherwise.
|
|
445
|
+
Even if they denote different elements, the function denoted by `ticket` need
|
|
446
|
+
not be injective, so `ticket(alice)` and `ticket(bob)` may still denote the same
|
|
447
|
+
element. A conventional first-order theory must add unique-name and free-
|
|
448
|
+
constructor axioms to rule out those interpretations.
|
|
449
|
+
|
|
450
|
+
In the Herbrand universe, `alice` and `bob` are different because they are
|
|
451
|
+
different ground terms. Compound terms are free constructors, so
|
|
452
|
+
`ticket(alice)` and `ticket(bob)` are also different. This makes unification,
|
|
453
|
+
read-back, generated witness terms, and proof explanations agree on identity by
|
|
454
|
+
construction. The program can therefore treat syntax as inspectable data
|
|
455
|
+
without first defining an external domain and an interpretation function.
|
|
456
|
+
|
|
457
|
+
This choice does not claim that differently named terms must denote different
|
|
458
|
+
entities in every application. When two names refer to the same real-world
|
|
459
|
+
entity, a Deriva program should represent that relationship explicitly, for
|
|
460
|
+
example with `same_as/2`, or normalize both names to one canonical term. The
|
|
461
|
+
Herbrand layer keeps the representation unambiguous; application rules state
|
|
462
|
+
the intended real-world equivalences.
|
|
463
|
+
|
|
464
|
+
### 8.2 Variables and quantification
|
|
465
|
+
|
|
466
|
+
Variables do not range over external objects, records, pointers, or host-language values. In the logical reading, variables range over Herbrand terms. A rule is implicitly universally quantified over its variables. A selected goal is existential in the usual logic-programming sense: Deriva searches for substitutions of its variables by Herbrand terms that make the goal true with respect to the program.
|
|
467
|
+
|
|
468
|
+
Deriva has no blank nodes and no existential variables in rule heads. Existential-style consequences SHOULD be represented by explicit Herbrand witness terms written directly in rule heads:
|
|
421
469
|
|
|
422
470
|
```deriva
|
|
423
471
|
has_parent(Child, parent_of(Child)) :-
|
|
@@ -429,27 +477,27 @@ registration(Student, Course, registration_of(Student, Course)) :-
|
|
|
429
477
|
|
|
430
478
|
These rules may derive `parent_of(alice)` or `registration_of(alice, logic)` as ordinary visible Herbrand terms. The witness is deterministic: the same functor and inputs produce the same term, while different inputs produce different terms by normal syntactic identity. This is the practical executable form of existential-style consequences in Deriva; it does not introduce hidden blank nodes or special quantifier syntax.
|
|
431
479
|
|
|
432
|
-
### 8.
|
|
480
|
+
### 8.3 Equality, identity, and unification
|
|
433
481
|
|
|
434
482
|
Because the domain is Herbrand, equality in the pure language is syntactic identity of terms after substitution. Two distinct atom constants are distinct. Two compound terms are equal only when they have the same functor, the same arity, and pairwise equal arguments. Lists follow the same rule through their `[]` and `./2` representation.
|
|
435
483
|
|
|
436
|
-
Operationally,
|
|
484
|
+
Operationally, Deriva uses first-order unification to find substitutions. The implementation does not perform an occurs check, so cyclic terms are not part of the portable Herbrand reading even if a particular implementation can temporarily construct recursive bindings internally. Portable programs SHOULD avoid relying on occurs-check-sensitive cases such as `eq(X, f(X))`.
|
|
437
485
|
|
|
438
|
-
### 8.
|
|
486
|
+
### 8.4 Goal-directed execution versus model-theoretic meaning
|
|
439
487
|
|
|
440
|
-
|
|
488
|
+
Deriva's CLI and library evaluator are goal-directed. They try to prove requested goals by resolving them against facts, rules, and built-ins, using clause order, goal order, indexing, tabling, and deterministic built-in execution. This operational strategy is intended to enumerate answers that are true in the least Herbrand model for the pure Horn-clause fragment, but it is not a complete bottom-up model enumerator. Non-terminating recursion or infinite generators can prevent an answer from being found even when the answer belongs to the least Herbrand model.
|
|
441
489
|
|
|
442
490
|
Default CLI output is also a host behavior, not a separate semantics. It asks broad materialization goals, suppresses duplicates, excludes source facts, keeps ground answers, and prints selected consequences. Embedders can still access the goal-directed solver directly through the implementation API.
|
|
443
491
|
|
|
444
|
-
### 8.
|
|
492
|
+
### 8.5 Built-ins and operational extensions
|
|
445
493
|
|
|
446
494
|
Built-ins are specified relations or operations added to the Herbrand core. A built-in call in a goal has the syntax of an atomic formula, but its success relation is specified procedurally here rather than by source clauses. Some built-ins, such as `eq/2`, `append/3`, `member/2`, and `length/2`, can be understood as relations over Herbrand terms. Others, such as arithmetic, string matching, date/time predicates, aggregation, `once/1`, and negation-as-failure, are operational extensions whose behavior is defined by this specification rather than by pure least-Herbrand-model semantics alone.
|
|
447
495
|
|
|
448
|
-
Arithmetic and string built-ins do not introduce a separate semantic universe. They inspect the lexical values of already represented Herbrand constants and, when they succeed, bind output arguments to
|
|
496
|
+
Arithmetic and string built-ins do not introduce a separate semantic universe. They inspect the lexical values of already represented Herbrand constants and, when they succeed, bind output arguments to Deriva terms such as numbers, strings, or atom constants. For example, `add(2, 3, X)` may bind `X` to the number term `5`; it does not mean that variables range over host-language numbers outside the Herbrand universe.
|
|
449
497
|
|
|
450
498
|
Negation-as-failure `not(Goal)` is especially operational: it succeeds when the current goal-directed search finds no solution for `Goal`. It is not classical negation and should not be read as adding negative facts to the Herbrand model. Programs using negation SHOULD keep the negated goal sufficiently ground and finite.
|
|
451
499
|
|
|
452
|
-
### 8.
|
|
500
|
+
### 8.6 Stratified negation
|
|
453
501
|
|
|
454
502
|
Portable programs using user-defined predicates under `not/1` SHOULD be **stratified**. A program is stratified when no predicate depends negatively on itself, either directly or through a cycle of other predicate dependencies. In a stratified program, predicates can be assigned strata so that positive dependencies stay in the same or a lower stratum and negative dependencies point strictly to a lower stratum.
|
|
455
503
|
|
|
@@ -544,7 +592,7 @@ Comparisons interpret numeric-looking terms numerically. Other scalar terms are
|
|
|
544
592
|
| `lowercase(Text, Out)`, `uppercase(Text, Out)`, `trim(Text, Out)` | Text normalization helpers. |
|
|
545
593
|
| `number_string(Number, String)` | Converts a number to a string or parses a numeric string into a number. |
|
|
546
594
|
| `atom_string(Atom, String)` | Converts between atom constants and strings. |
|
|
547
|
-
| `term_string(Term, String)` | Renders a ground term as its
|
|
595
|
+
| `term_string(Term, String)` | Renders a ground term as its Deriva source string. |
|
|
548
596
|
|
|
549
597
|
### 9.7 Lists
|
|
550
598
|
|
|
@@ -616,15 +664,15 @@ The first goal can yield `holds((name(alice, "Alice"), knows(alice, bob)), name(
|
|
|
616
664
|
|
|
617
665
|
## 10. Implementation-specific built-ins
|
|
618
666
|
|
|
619
|
-
Implementations MAY provide additional built-ins beyond the standard predicates listed above. Such built-ins are **implementation-specific built-ins**. They are useful for embedding
|
|
667
|
+
Implementations MAY provide additional built-ins beyond the standard predicates listed above. Such built-ins are **implementation-specific built-ins**. They are useful for embedding Deriva in particular host environments, exposing efficient finite-domain solvers, or providing domain-specific relations for applications.
|
|
620
668
|
|
|
621
|
-
Implementation-specific built-ins are not required for conformance to this specification. A portable
|
|
669
|
+
Implementation-specific built-ins are not required for conformance to this specification. A portable Deriva program SHOULD NOT depend on one unless the target implementation explicitly documents it.
|
|
622
670
|
|
|
623
671
|
An implementation-specific built-in SHOULD obey the same surface-language discipline as standard built-ins:
|
|
624
672
|
|
|
625
673
|
- it is called using ordinary atomic-formula syntax, for example `some_extension(A, B)`;
|
|
626
|
-
- its arguments and results are
|
|
627
|
-
- it succeeds, fails, and binds variables as a relation over
|
|
674
|
+
- its arguments and results are Deriva terms from the Herbrand universe;
|
|
675
|
+
- it succeeds, fails, and binds variables as a relation over Deriva terms;
|
|
628
676
|
- it SHOULD document its intended modes, especially which arguments must be ground before it runs deterministically;
|
|
629
677
|
- it MUST NOT change the meaning of ordinary facts, rules, unification, or standard built-ins.
|
|
630
678
|
|
|
@@ -638,7 +686,7 @@ Declarations are written as ordinary facts, but the host treats them specially.
|
|
|
638
686
|
|
|
639
687
|
### 11.1 Automatic hybrid reasoning
|
|
640
688
|
|
|
641
|
-
|
|
689
|
+
Deriva automatically combines ordinary goal-directed resolution with tabled
|
|
642
690
|
resolution. Predicate dependency cycles are detected when a program is loaded,
|
|
643
691
|
including dependencies inside conjunctions, negation, `once/1`, `forall/2`, and
|
|
644
692
|
aggregation goals. Positive recursive predicate groups, including directly
|
|
@@ -702,9 +750,9 @@ The example documents the common checking/generation mode where the list is supp
|
|
|
702
750
|
|
|
703
751
|
## 12. Deriva Sockets
|
|
704
752
|
|
|
705
|
-
A **
|
|
753
|
+
A **Deriva Socket** is a declared semantic opening in a Deriva program where facts, rules, tools, datasets, or agents can plug in knowledge through an explicit contract while preserving Deriva-readable reasoning and explanations.
|
|
706
754
|
|
|
707
|
-
The term follows the ordinary socket pattern: a socket defines a place where a matching provider can connect. In
|
|
755
|
+
The term follows the ordinary socket pattern: a socket defines a place where a matching provider can connect. In Deriva, the matching part is knowledge. A socket identifies what shape of knowledge a program expects; a plug identifies which provider supplies it. This separates reasoning logic from knowledge providers and makes composition boundaries visible as Deriva data.
|
|
708
756
|
|
|
709
757
|
In this specification, sockets are a portable **programming pattern** expressed with ordinary facts. The core solver does not give `socket/2`, `plug/2`, `provides/1`, or `requires/1` special proof-search behavior unless a host explicitly documents such an extension. Because they are ordinary facts, socket declarations remain readable, inspectable, explainable, and safe to ignore by hosts that do not validate them.
|
|
710
758
|
|
|
@@ -719,7 +767,7 @@ provides(Signature).
|
|
|
719
767
|
requires(Signature).
|
|
720
768
|
```
|
|
721
769
|
|
|
722
|
-
`Name` and `Provider` are ordinary
|
|
770
|
+
`Name` and `Provider` are ordinary Deriva terms, usually atom constants. `Contract` is an ordinary Deriva term that describes the expected or offered knowledge. A portable signature form is:
|
|
723
771
|
|
|
724
772
|
```deriva
|
|
725
773
|
predicate(Predicatename, Arity)
|
|
@@ -755,21 +803,21 @@ ancestor(X, Z) :-
|
|
|
755
803
|
ancestor(Y, Z).
|
|
756
804
|
```
|
|
757
805
|
|
|
758
|
-
The `ancestor/2` rules do not depend on a particular storage mechanism for `parent/2`. In a small test, the provider may be the same file. In an embedded host, it may be a database adapter, a document extractor, a remote service, or another
|
|
806
|
+
The `ancestor/2` rules do not depend on a particular storage mechanism for `parent/2`. In a small test, the provider may be the same file. In an embedded host, it may be a database adapter, a document extractor, a remote service, or another Deriva module. The socket facts make that boundary explicit without changing the logical meaning of the rules.
|
|
759
807
|
|
|
760
|
-
When
|
|
808
|
+
When Deriva derives `ancestor(pat, emma)`, the answer explanation can still refer to the source clauses that were actually used, for example facts for `parent/2` and rules for `ancestor/2`. The socket facts add an inspectable description of where such knowledge is intended to enter.
|
|
761
809
|
|
|
762
810
|
### 12.3 Sockets and AI agents
|
|
763
811
|
|
|
764
|
-
|
|
812
|
+
Deriva Sockets are especially useful for AI-facing systems. An AI agent can extract or propose candidate claims, but those claims should enter a reasoning program as explicit Deriva facts or rules through a declared socket rather than as opaque text. Deriva can then check the claims against other facts and rules, derive consequences, and optionally return ordinary `why/2` explanations.
|
|
765
813
|
|
|
766
|
-
This gives a clear division of labor: AI can help generate, translate, and connect knowledge;
|
|
814
|
+
This gives a clear division of labor: AI can help generate, translate, and connect knowledge; Deriva can represent, check, and explain the reasoning; sockets define the boundary between them.
|
|
767
815
|
|
|
768
816
|
## 13. Output and read-back profile
|
|
769
817
|
|
|
770
818
|
Normal answer output prints one resolved answer term followed by a period. Strings are double-quoted; atom constants are quoted when needed; lists use list syntax; compound terms use functor notation. Host interfaces MAY provide an option such as `--proof` to add `why/2` explanation facts; this option MUST NOT change the answers found. Host interfaces MAY also provide a non-fatal warning option such as `--warnings` for portability diagnostics such as unstratified negation; this option MUST NOT change the answers found.
|
|
771
819
|
|
|
772
|
-
Output SHOULD be accepted as
|
|
820
|
+
Output SHOULD be accepted as Deriva input when it contains only supported term syntax. Explanations are ordinary Deriva facts, so answer output can be read back and processed by Deriva.
|
|
773
821
|
|
|
774
822
|
Default host output behavior is:
|
|
775
823
|
|
|
@@ -783,11 +831,11 @@ Default host output behavior is:
|
|
|
783
831
|
|
|
784
832
|
### 13.1 Explanation output
|
|
785
833
|
|
|
786
|
-
When proof output is enabled, each answer SHOULD be followed by a machine-readable `why/2` fact. Explanation output is ordinary
|
|
834
|
+
When proof output is enabled, each answer SHOULD be followed by a machine-readable `why/2` fact. Explanation output is ordinary Deriva syntax whose second argument is a nested abstract proof term such as `proof(goal(G), by(Method), bindings(Bindings), uses(Proofs))`; implementations SHOULD print `goal(...)` and `by(...)` on separate lines for readability. A proof term preserves the answer goal, derivation method, relevant bindings, and nested uses while omitting proof IDs. User clauses SHOULD be referenced explicitly as `fact(Filename, clause(N))` or `rule(Filename, clause(N))`, where `N` is the 1-based clause number within that source. Built-ins SHOULD be referenced as `builtin(Name, Arity)` because they do not come from source clauses. Explanation output is outside the logical semantics of the input program and MUST NOT change the set of answers.
|
|
787
835
|
|
|
788
836
|
## 14. Conformance
|
|
789
837
|
|
|
790
|
-
A conforming
|
|
838
|
+
A conforming Deriva implementation supports the standard language described above as one conformance surface rather than as separate core and extension profiles. This includes:
|
|
791
839
|
|
|
792
840
|
- lexical syntax described above;
|
|
793
841
|
- facts and definite clauses;
|
|
@@ -804,11 +852,11 @@ A conforming deriva implementation supports the standard language described abov
|
|
|
804
852
|
|
|
805
853
|
Browser execution, package layout, CLI URL loading, and any implementation-specific built-ins described in host documentation are outside this conformance surface unless separately standardized.
|
|
806
854
|
|
|
807
|
-
Conformance cases live in the repository under `test/conformance/`. They are run by `npm test` before the example suite, and can be run alone with `node test/run-conformance.mjs`. Positive cases have input programs under `test/conformance/cases/` and exact expected standard-output files under `test/conformance/expected/`; both use `.pl` so expected output remains
|
|
855
|
+
Conformance cases live in the repository under `test/conformance/`. They are run by `npm test` before the example suite, and can be run alone with `node test/run-conformance.mjs`. Positive cases have input programs under `test/conformance/cases/` and exact expected standard-output files under `test/conformance/expected/`; both use `.pl` so expected output remains Deriva-readable. Expected-error cases live under `test/conformance/errors/` with exact messages under `test/conformance/expected-errors/`. Expected-warning cases live under `test/conformance/warnings/` with exact `--warnings` stdout and stderr files under `test/conformance/expected-warnings/`. Proof cases live under `test/conformance/proofs/` with exact explanation output under `test/conformance/expected-proofs/`. The corpus is grouped by language area, including arithmetic, strings, lists, terms, atoms, variables, negation, declarations, materialization, rules, syntax, and errors.
|
|
808
856
|
|
|
809
857
|
## 15. Relationship to ISO Prolog
|
|
810
858
|
|
|
811
|
-
|
|
859
|
+
Deriva source is intended to be familiar to Prolog readers and uses ISO Prolog-compatible variable and quoted-atom spelling, but Deriva is not ISO Prolog. Notable differences include:
|
|
812
860
|
- no operators or operator declarations;
|
|
813
861
|
- no zero-arity compound syntax such as `nil()`;
|
|
814
862
|
- no cut;
|
|
@@ -819,7 +867,7 @@ deriva source is intended to be familiar to Prolog readers and uses ISO Prolog-c
|
|
|
819
867
|
- no variables in functor or predicate position;
|
|
820
868
|
- no occurs check in unification.
|
|
821
869
|
|
|
822
|
-
Programs intended to be portable to
|
|
870
|
+
Programs intended to be portable to Deriva SHOULD use uppercase or underscore variables, avoid ISO-specific features that Deriva does not implement, and keep terms explicit. Atom names that are not plain lowercase-starting names or graphic atom tokens SHOULD be written as quoted atoms, for example `'a-b'` or `'<abc>'`.
|
|
823
871
|
|
|
824
872
|
## 16. Examples
|
|
825
873
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Numeric builtins for integer-preserving arithmetic, floating point functions, comparisons, and ranges.
|
|
2
|
-
// The code keeps BigInt paths where possible so large
|
|
2
|
+
// The code keeps BigInt paths where possible so large Deriva integers remain exact.
|
|
3
3
|
import { compareIntegerText, deref, isDecimalInteger, lexicalValue, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
const unaryNames = ['neg', 'abs', 'sin', 'cos', 'tan', 'asin', 'acos', 'sqrt', 'floor', 'ceiling', 'trunc', 'rounded', 'exp', 'log'];
|
|
@@ -200,7 +200,7 @@ function nextDown(value) {
|
|
|
200
200
|
function logCompat(input) {
|
|
201
201
|
const value = Math.log(input);
|
|
202
202
|
// V8 and glibc libm differ by one ulp for a few values reached by the
|
|
203
|
-
// Newton-Raphson example. Align this pure-JS port with the native
|
|
203
|
+
// Newton-Raphson example. Align this pure-JS port with the native Deriva
|
|
204
204
|
// reference while leaving ordinary log(1), log(2), log(10), and log(e) alone.
|
|
205
205
|
if (input > 2.5 && input < 2.7182818 && value > 0.95 && value < 1.0) return nextUp(value);
|
|
206
206
|
if (input > 2.7182818 && input < 2.718281828459 && value > 0.999999999999 && value < 1.0) return nextDown(value);
|
package/src/builtins/terms.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Term-inspection builtins for reusable meta-programming over
|
|
1
|
+
// Term-inspection builtins for reusable meta-programming over Deriva terms.
|
|
2
2
|
import { atom, compound, deref, listFromItems, lexicalValue, numberTerm, properListItems, stringTerm, unify } from '../term.js';
|
|
3
3
|
|
|
4
4
|
export const termBuiltins = {
|
package/src/cli.js
CHANGED
package/src/explain.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Deriva proof output helpers.
|
|
2
2
|
// The explanation printer replays a successful goal against the program and emits
|
|
3
|
-
// ordinary
|
|
3
|
+
// ordinary Deriva facts with nested proof terms. Explanations are therefore both
|
|
4
4
|
// human-readable and machine-readable.
|
|
5
5
|
import { COMPOUND, Env, Term, VAR, deref, flattenConjunction, freshTerm, termToString, unify, variantTerms } from './term.js';
|
|
6
6
|
import { selectClauseCandidates } from './program.js';
|
package/src/parser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Tokenizer and recursive-descent parser for the
|
|
1
|
+
// Tokenizer and recursive-descent parser for the Deriva source language.
|
|
2
2
|
// It preserves the compact Prolog-like syntax while producing Term objects for the solver.
|
|
3
3
|
import { atom, compound, cons, emptyList, numberTerm, stringTerm, variable } from './term.js';
|
|
4
4
|
|
package/src/solver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Depth-first
|
|
1
|
+
// Depth-first Deriva solver with builtin dispatch, memoization, and guarded recursion handling.
|
|
2
2
|
// Most semantic decisions still flow through unification; optimizations only select candidates earlier.
|
|
3
3
|
import { COMPOUND, Env, compound, copyResolved, flattenConjunction, freshTerm, termIsGround, termToString, unify, variantTerms } from './term.js';
|
|
4
4
|
import { createDefaultRegistry } from './builtins/registry.js';
|
|
@@ -113,7 +113,7 @@ export class Solver {
|
|
|
113
113
|
continue;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
//
|
|
116
|
+
// Deriva normally solves left-to-right, but ready deterministic builtins can
|
|
117
117
|
// be run early as pure filters. Stop at internal sentinels so rule-body
|
|
118
118
|
// active guards are released before the caller's remaining goals are seen.
|
|
119
119
|
const selectedIndex = selectReadyDeterministicBuiltin(goals, env, this.registry);
|
|
@@ -9,7 +9,7 @@ All conformance files live under topic directories such as `arithmetic/`, `lists
|
|
|
9
9
|
A normal positive case consists of:
|
|
10
10
|
|
|
11
11
|
- `conformance/cases/<name>.pl` — input program;
|
|
12
|
-
- `conformance/expected/<name>.pl` — exact expected standard output, stored as
|
|
12
|
+
- `conformance/expected/<name>.pl` — exact expected standard output, stored as Deriva-readable facts.
|
|
13
13
|
|
|
14
14
|
Expected-error cases consist of:
|
|
15
15
|
|
|
@@ -63,9 +63,9 @@ The runner executes normal materialized programs in-process through the public J
|
|
|
63
63
|
|
|
64
64
|
## Scope
|
|
65
65
|
|
|
66
|
-
The conformance corpus is a single
|
|
66
|
+
The conformance corpus is a single Deriva suite. It covers the standard language described by the language reference: lexical syntax, facts, definite clauses, first-order terms, lists, conjunction, structured unification, left-to-right goal-directed proof search, materialized output, read-back printing, standard built-ins, declarations, warnings, errors, proof output, and standard host behavior.
|
|
67
67
|
|
|
68
|
-
The suite deliberately does not separate `core` and `extension` profiles. Reusable built-ins such as arithmetic, strings, lists, aggregation, context terms, term inspection, and search control are part of the standard
|
|
68
|
+
The suite deliberately does not separate `core` and `extension` profiles. Reusable built-ins such as arithmetic, strings, lists, aggregation, context terms, term inspection, and search control are part of the standard Deriva conformance surface. Implementation-specific built-ins may still exist in downstream hosts, but they should have their own tests outside this corpus unless they are standardized.
|
|
69
69
|
|
|
70
70
|
## Updating expected output
|
|
71
71
|
|
|
@@ -56,7 +56,7 @@ export function buildConformanceReport() {
|
|
|
56
56
|
|
|
57
57
|
export function formatConformanceReport(report = buildConformanceReport()) {
|
|
58
58
|
const lines = [
|
|
59
|
-
'#
|
|
59
|
+
'# Deriva conformance report',
|
|
60
60
|
'',
|
|
61
61
|
'This report summarizes the file-based conformance corpus under `test/conformance/`.',
|
|
62
62
|
'',
|
package/test/run-regression.mjs
CHANGED
|
@@ -358,7 +358,7 @@ function documentationSyncCases() {
|
|
|
358
358
|
run: () => assertArrayEqual(findBrokenDocLinks(), [], 'broken documentation links'),
|
|
359
359
|
},
|
|
360
360
|
{
|
|
361
|
-
name: 'documentation uses
|
|
361
|
+
name: 'documentation uses Deriva source style',
|
|
362
362
|
run: () => assertArrayEqual(documentationSourceStyleIssues(), [], 'documentation source style'),
|
|
363
363
|
},
|
|
364
364
|
{
|