eyeling 1.12.3 → 1.12.5

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 CHANGED
@@ -27,6 +27,7 @@
27
27
  - [Epilogue](#epilogue)
28
28
  - [Appendix A — Eyeling user notes](#app-a)
29
29
  - [Appendix B — Notation 3: when facts can carry their own logic](#app-b)
30
+ - [Appendix C — N3 beyond Prolog: logic that survives the open web](#app-c)
30
31
 
31
32
  ---
32
33
 
@@ -124,7 +125,7 @@ If you want to follow the code in the same order Eyeling “thinks”, read:
124
125
  - deterministic Skolem IDs (head existentials + `log:skolem`) (`lib/skolem.js`)
125
126
  6. `lib/builtins.js` — builtin predicate evaluation plus shared literal/number/string/list helpers:
126
127
  - `makeBuiltins(deps)` dependency-injects engine hooks (unification, proving, deref, …)
127
- - exports `evalBuiltin(...)` and `isBuiltinPred(...)` back to the engine
128
+ - and returns `{ evalBuiltin, isBuiltinPred }` back to the engine
128
129
  - 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
129
130
  7. `lib/explain.js` — proof comments + `log:outputString` aggregation (fact ordering and pretty output).
130
131
  8. `lib/deref.js` — synchronous dereferencing for `log:content` / `log:semantics` (used by builtins and engine).
@@ -504,10 +505,10 @@ At each step:
504
505
  - apply the current substitution to it
505
506
  - attempt to satisfy it in three ways:
506
507
  1. built-ins
507
- 2. facts
508
- 3. backward rules
508
+ 2. backward rules
509
+ 3. facts
509
510
 
510
- Eyeling’s order is intentional: built-ins often bind variables cheaply; rules expand search trees.
511
+ 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.
511
512
 
512
513
  ### 8.3 Built-ins: return _deltas_, not full substitutions
513
514
 
@@ -539,9 +540,15 @@ Conjunction in N3 is order-insensitive, but many builtins are only useful once s
539
540
 
540
541
  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.
541
542
 
542
- ### 8.4 Loop prevention: a simple visited list
543
+ ### 8.4 Loop prevention: visited multiset with backtracking
543
544
 
544
- Eyeling prevents obvious infinite recursion by skipping a goal if it is already in the `visited` list. This is a pragmatic check; it doesn’t implement full tabling, but it avoids the most common “A depends on A” loops.
545
+ 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.
546
+
547
+ Implementation notes:
548
+
549
+ - 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).
550
+ - 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.
551
+ - This is not full tabling: it does not memoize answers, it only guards against immediate cycles (the common “A depends on A” loops).
545
552
 
546
553
  ### 8.5 Backward rules: indexed by head predicate
547
554
 
@@ -566,7 +573,7 @@ To reduce allocation pressure, Eyeling reuses a single fresh `Var(...)` object p
566
573
  The trail-based substitution store removes the biggest accidental quadratic cost (copying a growing substitution object at every step).
567
574
  In deep and branchy searches, the substitution trail still grows, and long variable-to-variable chains increase the work done by `applySubstTerm`.
568
575
 
569
- Eyeling currently keeps the full trail as-is during search and when emitting answers; it does not run a substitution compaction pass, and it does not perform explicit substitution composition.
576
+ 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.
570
577
 
571
578
  ---
572
579
 
@@ -1923,13 +1930,13 @@ Logic & reasoning background (Wikipedia):
1923
1930
 
1924
1931
  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.
1925
1932
 
1926
- 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
+ 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.
1927
1934
 
1928
- 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
+ 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/).
1929
1936
 
1930
1937
  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).
1931
1938
 
1932
- This style of rule-writing is more than syntactic sugar. 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
+ 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/).
1933
1940
 
1934
1941
  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).
1935
1942
 
@@ -1941,4 +1948,23 @@ The comparison with older tools is historically instructive. Cwm (Closed World M
1941
1948
 
1942
1949
  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.
1943
1950
 
1944
- 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.
1951
+ 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.
1952
+
1953
+ ---
1954
+
1955
+ ## Appendix C — N3 beyond Prolog: logic that survives the open web
1956
+
1957
+ At first glance, an N3 rule set can feel familiar if you’ve used Prolog: variables, unification, and rules that read like “if this pattern holds, then that pattern follows.” But N3 is not just “logic programming with a different syntax.” It is logic shaped for a different environment: not a single program with a single database, but a world of distributed graphs that can be published, merged, and cited across boundaries.
1958
+
1959
+ That change of environment forces a change in what “beyond Prolog” even means. It is less about being more powerful in the abstract, and more about being *more portable as meaning* — logic that stays connected when it moves between documents, vocabularies, and authors.
1960
+
1961
+ Several design moves push N3 into that web-native space:
1962
+
1963
+ - **Global identity is the default.** Names are IRIs. A rule does not merely compute with local symbols; it operates over identifiers meant to be shared across datasets.
1964
+ - **Graphs are the unit of exchange.** The input is a graph; the output is a graph. Inference produces new triples rather than hidden internal state, so results can travel the same way the facts do.
1965
+ - **Statements can be treated as data.** Quoted graphs let you talk *about* assertions: claims, policies, provenance, “this source says …,” “this formula implies …,” and other meta-level structure that is awkward in a plain predicate database.
1966
+ - **Rules can be publishable artifacts.** Rules can live alongside data as text, be versioned, reviewed, and reused — the “meaning” is not forced back into an external codebase.
1967
+ - **Web-like computation can be pulled into rule bodies.** Built-ins make room for the small computations that real integration needs (strings, lists, comparisons), and some N3 workflows even treat IRIs as pointers to more knowledge.
1968
+
1969
+ In that sense, Prolog is a superb engine for proving things *inside* a chosen world. N3 is a way to write rules so they keep working *across* worlds: across documents, across graph boundaries, and across the open-ended growth of linked data. When an engine like Eyeling solves rule bodies with a Prolog-like prover but still saturates forward consequences, it’s exactly this bridge: Prolog-style execution serving a web-scale, graph-first notion of meaning.
1970
+
@@ -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
+ }.
@@ -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
  } .
@@ -0,0 +1,10 @@
1
+ @prefix : <http://example.org/> .
2
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
3
+
4
+ :patch1 :result {
5
+ :alice a :Person .
6
+ :bob a :Person .
7
+ :alice :age 31 .
8
+ :alice :status :ActiveStatus .
9
+ :alice :verified true .
10
+ } .
@@ -0,0 +1,55 @@
1
+ # ===================================================================
2
+ # N3 graph patch example
3
+ # - Removes every triple from :source that is also present in :delete
4
+ # - Then adds all triples from :insert
5
+ # Uses only N3 builtins from the W3C spec:
6
+ # log:collectAllIn, log:conjunction, log:notIncludes
7
+ # ===================================================================
8
+
9
+ @prefix : <http://example.org/> .
10
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
11
+
12
+ :patch1
13
+ :source {
14
+ :alice a :Person ;
15
+ :age 30 ;
16
+ :status :OldStatus ;
17
+ :email "alice@example.org" .
18
+ :bob a :Person .
19
+ } ;
20
+ :delete {
21
+ :alice :age 30 ;
22
+ :status :OldStatus ;
23
+ :email "alice@example.org" .
24
+ } ;
25
+ :insert {
26
+ :alice :age 31 ;
27
+ :status :ActiveStatus ;
28
+ :verified true .
29
+ } .
30
+
31
+ {
32
+ :patch1 :source ?G ;
33
+ :delete ?Del ;
34
+ :insert ?Ins .
35
+
36
+ # Collect exactly the triples from ?G that are NOT included in the delete graph term ?Del
37
+ ( { ?S ?P ?O . }
38
+ {
39
+ ?S ?P ?O .
40
+ ?Del log:notIncludes { ?S ?P ?O . } .
41
+ }
42
+ ?keptTriples
43
+ ) log:collectAllIn ?G .
44
+
45
+ # Merge kept graph terms back into a single graph term
46
+ ?keptTriples log:conjunction ?keptGraph .
47
+
48
+ # Add the insert graph term
49
+ ( ?keptGraph ?Ins ) log:conjunction ?patched .
50
+ }
51
+ =>
52
+ {
53
+ :patch1 :result ?patched .
54
+ } .
55
+