eyeling 1.12.2 → 1.12.4
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/HANDBOOK.md +47 -9
- package/examples/ackermann.n3 +89 -0
- package/examples/fibonacci.n3 +2 -0
- package/examples/output/ackermann.n3 +15 -0
- package/examples/output/fibonacci.n3 +1 -0
- package/eyeling.js +358 -139
- package/lib/builtins.js +41 -33
- package/lib/engine.js +290 -98
- package/lib/parser.js +0 -2
- package/lib/prelude.js +27 -4
- package/lib/rules.js +0 -2
- package/package.json +1 -1
package/HANDBOOK.md
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
- [Chapter 16 — Extending Eyeling (without breaking it)](#ch16)
|
|
27
27
|
- [Epilogue](#epilogue)
|
|
28
28
|
- [Appendix A — Eyeling user notes](#app-a)
|
|
29
|
+
- [Appendix B — Notation 3: when facts can carry their own logic](#app-b)
|
|
29
30
|
|
|
30
31
|
---
|
|
31
32
|
|
|
@@ -123,7 +124,7 @@ If you want to follow the code in the same order Eyeling “thinks”, read:
|
|
|
123
124
|
- deterministic Skolem IDs (head existentials + `log:skolem`) (`lib/skolem.js`)
|
|
124
125
|
6. `lib/builtins.js` — builtin predicate evaluation plus shared literal/number/string/list helpers:
|
|
125
126
|
- `makeBuiltins(deps)` dependency-injects engine hooks (unification, proving, deref, …)
|
|
126
|
-
-
|
|
127
|
+
- and returns `{ evalBuiltin, isBuiltinPred }` back to the engine
|
|
127
128
|
- includes `materializeRdfLists(...)`, a small pre-pass that rewrites _anonymous_ `rdf:first`/`rdf:rest` linked lists into concrete N3 list terms so `list:*` builtins can work uniformly
|
|
128
129
|
7. `lib/explain.js` — proof comments + `log:outputString` aggregation (fact ordering and pretty output).
|
|
129
130
|
8. `lib/deref.js` — synchronous dereferencing for `log:content` / `log:semantics` (used by builtins and engine).
|
|
@@ -503,10 +504,10 @@ At each step:
|
|
|
503
504
|
- apply the current substitution to it
|
|
504
505
|
- attempt to satisfy it in three ways:
|
|
505
506
|
1. built-ins
|
|
506
|
-
2.
|
|
507
|
-
3.
|
|
507
|
+
2. backward rules
|
|
508
|
+
3. facts
|
|
508
509
|
|
|
509
|
-
Eyeling’s order is intentional: built-ins often bind variables cheaply; rules expand search
|
|
510
|
+
Eyeling’s order is intentional: built-ins often bind variables cheaply; backward rules expand the search tree (and enable recursion); facts are tried last as cheap terminal matches.
|
|
510
511
|
|
|
511
512
|
### 8.3 Built-ins: return _deltas_, not full substitutions
|
|
512
513
|
|
|
@@ -538,9 +539,15 @@ Conjunction in N3 is order-insensitive, but many builtins are only useful once s
|
|
|
538
539
|
|
|
539
540
|
That second case matters for “satisfiable but non-enumerating” builtins (e.g., some `log:` helpers) where early vacuous success would otherwise prevent later goals from ever binding the variables the builtin needs.
|
|
540
541
|
|
|
541
|
-
### 8.4 Loop prevention:
|
|
542
|
+
### 8.4 Loop prevention: visited multiset with backtracking
|
|
542
543
|
|
|
543
|
-
Eyeling
|
|
544
|
+
Eyeling avoids obvious infinite recursion by recording each (substituted) goal it is currently trying in a per-branch _visited_ structure. If the same goal is encountered again on the same proof branch, Eyeling skips it.
|
|
545
|
+
|
|
546
|
+
Implementation notes:
|
|
547
|
+
|
|
548
|
+
- The visited structure is a `Map` from _goal key_ to a reference count, plus a trail array. This makes it cheap to check (`O(1)` average) and cheap to roll back on backtracking (just like the substitution trail).
|
|
549
|
+
- Keys are _structural_. Atoms use stable IDs; lists use element keys; variables use their identity (so two different variables are **not** conflated). This keeps the cycle check conservative and avoids accidental pruning.
|
|
550
|
+
- This is not full tabling: it does not memoize answers, it only guards against immediate cycles (the common “A depends on A” loops).
|
|
544
551
|
|
|
545
552
|
### 8.5 Backward rules: indexed by head predicate
|
|
546
553
|
|
|
@@ -565,7 +572,7 @@ To reduce allocation pressure, Eyeling reuses a single fresh `Var(...)` object p
|
|
|
565
572
|
The trail-based substitution store removes the biggest accidental quadratic cost (copying a growing substitution object at every step).
|
|
566
573
|
In deep and branchy searches, the substitution trail still grows, and long variable-to-variable chains increase the work done by `applySubstTerm`.
|
|
567
574
|
|
|
568
|
-
Eyeling currently keeps the full trail as-is during search
|
|
575
|
+
Eyeling currently keeps the full trail as-is during search. When emitting a solution, it runs a lightweight compaction pass (via `gcCollectVarsInGoals(...)` / `gcCompactForGoals(...)`) so only bindings reachable from the answer variables and remaining goals are kept. It still does not perform general substitution composition/normalization during search.
|
|
569
576
|
|
|
570
577
|
---
|
|
571
578
|
|
|
@@ -871,7 +878,8 @@ These are “function-like” relations where the subject is usually a list and
|
|
|
871
878
|
|
|
872
879
|
**Shape:** `( $x1 $x2 ... ) math:sum $total`
|
|
873
880
|
|
|
874
|
-
- Subject must be a list of
|
|
881
|
+
- Subject must be a list of numeric terms (the list may be empty or a singleton).
|
|
882
|
+
- Empty list sums to **0**.
|
|
875
883
|
- Computes the numeric sum.
|
|
876
884
|
- Chooses an output datatype based on the “widest” numeric datatype seen among inputs and (optionally) the object position; integers stay integers unless the result is non-integer.
|
|
877
885
|
|
|
@@ -879,7 +887,9 @@ These are “function-like” relations where the subject is usually a list and
|
|
|
879
887
|
|
|
880
888
|
**Shape:** `( $x1 $x2 ... ) math:product $total`
|
|
881
889
|
|
|
882
|
-
-
|
|
890
|
+
- Subject must be a list of numeric terms (the list may be empty or a singleton).
|
|
891
|
+
- Empty list product is **1**.
|
|
892
|
+
- Same datatype conventions as `math:sum`, but multiplies.
|
|
883
893
|
|
|
884
894
|
#### `math:difference`
|
|
885
895
|
|
|
@@ -1910,3 +1920,31 @@ Logic & reasoning background (Wikipedia):
|
|
|
1910
1920
|
- [https://en.wikipedia.org/wiki/Prolog](https://en.wikipedia.org/wiki/Prolog)
|
|
1911
1921
|
- [https://en.wikipedia.org/wiki/Datalog](https://en.wikipedia.org/wiki/Datalog)
|
|
1912
1922
|
- [https://en.wikipedia.org/wiki/Skolem_normal_form](https://en.wikipedia.org/wiki/Skolem_normal_form)
|
|
1923
|
+
|
|
1924
|
+
---
|
|
1925
|
+
|
|
1926
|
+
<a id="app-b"></a>
|
|
1927
|
+
|
|
1928
|
+
## Appendix B — Notation 3: when facts can carry their own logic
|
|
1929
|
+
|
|
1930
|
+
RDF succeeded by making a radical constraint feel natural: reduce meaning to small, uniform statements—triples—that can be published, merged, and queried across boundaries. A triple does not presume a database schema, a programming language, or a particular application. It presumes only that names (IRIs) can be shared, and that graphs can be combined.
|
|
1931
|
+
|
|
1932
|
+
That strength also marks RDF’s limit. The moment a graph is expected to _do_ something—normalize values, reconcile vocabularies, derive implied relationships, enforce a policy, compute a small transformation—logic tends to migrate into code. The graph becomes an inert substrate while the decisive semantics hide in scripts, services, ETL pipelines, or bespoke rule engines. What remains portable is the data; what often becomes non-portable is the meaning.
|
|
1933
|
+
|
|
1934
|
+
Notation 3 (N3) sits precisely at that seam. It remains a readable way to write RDF, but it also treats _graphs themselves_ as objects that can be described, matched, and related. The N3 Community Group’s specification presents N3 as an assertion and logic language that extends RDF rather than replacing it: [https://w3c.github.io/N3/spec/](https://w3c.github.io/N3/spec/).
|
|
1935
|
+
|
|
1936
|
+
The essential move is quotation: writing a graph inside braces as a thing that can be discussed. Once graphs can be quoted, rules become graph-to-graph transformations. The familiar implication form, `{ … } => { … } .`, reads as a piece of prose: whenever the antecedent pattern holds, the consequent pattern follows. Tim Berners-Lee’s design note frames this as a web-friendly logic with variables and nested graphs: [https://www.w3.org/DesignIssues/Notation3.html](https://www.w3.org/DesignIssues/Notation3.html).
|
|
1937
|
+
|
|
1938
|
+
This style of rule-writing makes rules first-class, publishable artifacts. It keeps the unit of exchange stable. Inputs are RDF graphs; outputs are RDF graphs. Inference produces new triples rather than hidden internal state. Rule sets can be versioned alongside data, reviewed as text, and executed by different engines that implement the same semantics. That portability theme runs back to the original W3C Team Submission: [https://www.w3.org/TeamSubmission/n3/](https://www.w3.org/TeamSubmission/n3/).
|
|
1939
|
+
|
|
1940
|
+
Practical reasoning also depends on computation: lists, strings, math, comparisons, and the other “small operations” that integration work demands. N3 addresses this by standardizing built-ins—predicates with predefined behavior that can be used inside rule bodies while preserving the declarative, graph-shaped idiom. The built-ins report is here: [https://w3c.github.io/N3/reports/20230703/builtins.html](https://w3c.github.io/N3/reports/20230703/builtins.html).
|
|
1941
|
+
|
|
1942
|
+
Testing is where rule languages either converge or fragment. Different implementations can drift on scoping, blank nodes, quantification, and built-in behavior. N3’s recent direction has been toward explicit, testable semantics, documented separately as model-theoretic foundations: [https://w3c.github.io/N3/reports/20230703/semantics.html](https://w3c.github.io/N3/reports/20230703/semantics.html).
|
|
1943
|
+
|
|
1944
|
+
In that context, public conformance suites become more than scoreboards: they are the mechanism by which interoperability becomes measurable. The community test suite lives at [https://codeberg.org/phochste/notation3tests/](https://codeberg.org/phochste/notation3tests/), with comparative results published in its report: [https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md](https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md).
|
|
1945
|
+
|
|
1946
|
+
The comparison with older tools is historically instructive. Cwm (Closed World Machine) was an early, influential RDF data processor and forward-chaining reasoner—part of the lineage that treated RDF (often written in N3) as something executable: [https://www.w3.org/2000/10/swap/doc/cwm](https://www.w3.org/2000/10/swap/doc/cwm).
|
|
1947
|
+
|
|
1948
|
+
What motivates Notation 3, in the end, is architectural restraint. It refuses to let “logic” become merely a private feature of an application stack. It keeps meaning close to the graph: rules are expressed as graph patterns; results are expressed as triples; computation is pulled in through well-defined built-ins rather than arbitrary code. This produces a style of working where integration and inference are not sidecar scripts, but publishable artifacts—documents that can be inspected, shared, tested, and reused.
|
|
1949
|
+
|
|
1950
|
+
In that sense, N3 is less a bid to make the web “smarter” than a bid to make meaning _portable_: not only facts that travel, but also the explicit steps by which facts can be connected, extended, and made actionable—without abandoning the simplicity that made triples travel in the first place.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# ==================
|
|
2
|
+
# Ackermann function
|
|
3
|
+
# ==================
|
|
4
|
+
#
|
|
5
|
+
# See https://en.wikipedia.org/wiki/Ackermann_function
|
|
6
|
+
|
|
7
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#>.
|
|
8
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#>.
|
|
9
|
+
@prefix : <https://eyereasoner.github.io/ns#>.
|
|
10
|
+
|
|
11
|
+
# ackermann(x, y)
|
|
12
|
+
{
|
|
13
|
+
(?X ?Y) :ackermann ?A.
|
|
14
|
+
} <= {
|
|
15
|
+
(?Y 3) math:sum ?B.
|
|
16
|
+
(?X ?B 2) :ackermann ?C.
|
|
17
|
+
(?C 3) math:difference ?A.
|
|
18
|
+
}.
|
|
19
|
+
|
|
20
|
+
# ackermann(x, y, z)
|
|
21
|
+
# succ (x=0)
|
|
22
|
+
{
|
|
23
|
+
(0 ?Y ?Z) :ackermann ?A.
|
|
24
|
+
} <= {
|
|
25
|
+
(?Y 1) math:sum ?A.
|
|
26
|
+
}.
|
|
27
|
+
|
|
28
|
+
# sum (x=1)
|
|
29
|
+
{
|
|
30
|
+
(1 ?Y ?Z) :ackermann ?A.
|
|
31
|
+
} <= {
|
|
32
|
+
(?Y ?Z) math:sum ?A.
|
|
33
|
+
}.
|
|
34
|
+
|
|
35
|
+
# product (x=2)
|
|
36
|
+
{
|
|
37
|
+
(2 ?Y ?Z) :ackermann ?A.
|
|
38
|
+
} <= {
|
|
39
|
+
(?Y ?Z) math:product ?A.
|
|
40
|
+
}.
|
|
41
|
+
|
|
42
|
+
# exponentiation (x=3), tetration (x=4), pentation (x=5), hexation (x=6), etc
|
|
43
|
+
{
|
|
44
|
+
(?X 0 ?Z) :ackermann 1.
|
|
45
|
+
} <= {
|
|
46
|
+
?X math:greaterThan 2.
|
|
47
|
+
}.
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
(?X ?Y ?Z) :ackermann ?A.
|
|
51
|
+
} <= {
|
|
52
|
+
?X math:greaterThan 2.
|
|
53
|
+
?Y math:notEqualTo 0.
|
|
54
|
+
(?Y 1) math:difference ?B.
|
|
55
|
+
(?X ?B ?Z) :ackermann ?C.
|
|
56
|
+
(?X 1) math:difference ?D.
|
|
57
|
+
(?D ?C ?Z) :ackermann ?A.
|
|
58
|
+
}.
|
|
59
|
+
|
|
60
|
+
# test
|
|
61
|
+
{
|
|
62
|
+
(0 0) :ackermann ?A0.
|
|
63
|
+
(0 6) :ackermann ?A1.
|
|
64
|
+
(1 2) :ackermann ?A2.
|
|
65
|
+
(1 7) :ackermann ?A3.
|
|
66
|
+
(2 2) :ackermann ?A4.
|
|
67
|
+
(2 9) :ackermann ?A5.
|
|
68
|
+
(3 4) :ackermann ?A6.
|
|
69
|
+
(3 1000) :ackermann ?A7.
|
|
70
|
+
(4 0) :ackermann ?A8.
|
|
71
|
+
(4 1) :ackermann ?A9.
|
|
72
|
+
#(4 2) :ackermann ?A10.
|
|
73
|
+
(5 0) :ackermann ?A11.
|
|
74
|
+
} => {
|
|
75
|
+
:test :is {
|
|
76
|
+
(0 0) :ackermann ?A0.
|
|
77
|
+
(0 6) :ackermann ?A1.
|
|
78
|
+
(1 2) :ackermann ?A2.
|
|
79
|
+
(1 7) :ackermann ?A3.
|
|
80
|
+
(2 2) :ackermann ?A4.
|
|
81
|
+
(2 9) :ackermann ?A5.
|
|
82
|
+
(3 4) :ackermann ?A6.
|
|
83
|
+
(3 1000) :ackermann ?A7.
|
|
84
|
+
(4 0) :ackermann ?A8.
|
|
85
|
+
(4 1) :ackermann ?A9.
|
|
86
|
+
#(4 2) :ackermann ?A10.
|
|
87
|
+
(5 0) :ackermann ?A11.
|
|
88
|
+
}.
|
|
89
|
+
}.
|
package/examples/fibonacci.n3
CHANGED
|
@@ -22,12 +22,14 @@
|
|
|
22
22
|
1 :fibonacci ?F1.
|
|
23
23
|
10 :fibonacci ?F10.
|
|
24
24
|
100 :fibonacci ?F100.
|
|
25
|
+
1000 :fibonacci ?F1000.
|
|
25
26
|
} => {
|
|
26
27
|
:test :is {
|
|
27
28
|
0 :fibonacci ?F0.
|
|
28
29
|
1 :fibonacci ?F1.
|
|
29
30
|
10 :fibonacci ?F10.
|
|
30
31
|
100 :fibonacci ?F100.
|
|
32
|
+
1000 :fibonacci ?F1000.
|
|
31
33
|
}.
|
|
32
34
|
}.
|
|
33
35
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@prefix : <https://eyereasoner.github.io/ns#> .
|
|
2
|
+
|
|
3
|
+
:test :is {
|
|
4
|
+
(0 0) :ackermann 1 .
|
|
5
|
+
(0 6) :ackermann 7 .
|
|
6
|
+
(1 2) :ackermann 4 .
|
|
7
|
+
(1 7) :ackermann 9 .
|
|
8
|
+
(2 2) :ackermann 7 .
|
|
9
|
+
(2 9) :ackermann 21 .
|
|
10
|
+
(3 4) :ackermann 125 .
|
|
11
|
+
(3 1000) :ackermann 85720688574901385675874003924800144844912384936442688595500031069628084089994889799455870305255668650207573833404251746014971622855385123487876620597588598431476542198593847883368596840498969135023633457224371799868655530139190140473324351568616503316569571821492337341283438653220995094697645344555005 .
|
|
12
|
+
(4 0) :ackermann 13 .
|
|
13
|
+
(4 1) :ackermann 65533 .
|
|
14
|
+
(5 0) :ackermann 65533 .
|
|
15
|
+
} .
|
|
@@ -5,4 +5,5 @@
|
|
|
5
5
|
1 :fibonacci 1 .
|
|
6
6
|
10 :fibonacci 55 .
|
|
7
7
|
100 :fibonacci 354224848179261915075 .
|
|
8
|
+
1000 :fibonacci 43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875 .
|
|
8
9
|
} .
|