eyeprolog 1.3.12 → 1.3.13

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 CHANGED
@@ -107,13 +107,14 @@ keeps both long-running filters such as `\+ phrase((..., Pattern), Sequence)`
107
107
  and repeated same-input probes bounded without turning memory safety into a
108
108
  per-call cache-eviction cost.
109
109
 
110
- Finite-tree unification also recognizes a conservative first-use case inspired
111
- by the local-variable optimization used by WAM-family systems. In a freshly
112
- renamed clause, a singleton head variable or a variable that first appears once
113
- in a direct `=/2` goal cannot already occur in the value it is about to receive,
114
- so that one binding can skip an otherwise redundant occurs traversal. Repeated
110
+ Finite-tree unification also accepts an internal proven-nonoccurrence hint: when
111
+ the solver can prove that a variable cannot occur in the value it is about to
112
+ receive, that binding skips an otherwise redundant occurs traversal. The main
113
+ source-level case is conservative first use in a freshly renamed clause, inspired
114
+ by the local-variable optimization used by WAM-family systems; native construction
115
+ paths such as relational `length/2` reuse the same unifier mechanism. Repeated
115
116
  variables, variables seen earlier in the clause, and ordinary public unification
116
- remain fully occurs-checked. This is a source-level proof, not a WAM-style
117
+ remain fully occurs-checked. This is a proof local to one binding, not a WAM-style
117
118
  local/global variable stack. `phrase/2` likewise supplies its fixed `[]`
118
119
  remainder directly to the grammar; `phrase/3` retains its delayed final output
119
120
  unification for steadfastness.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.12",
6
+ "version": "1.3.13",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/iso.js CHANGED
@@ -181,8 +181,8 @@ export const eyePrologLibraryBuiltins = {
181
181
 
182
182
  function* unification({ goal, env }) {
183
183
  const next = env.clone();
184
- const localFreshVariables = goal._localFreshVariables ?? null;
185
- if (unify(goal.args[0], goal.args[1], next, { localFreshVariables })) yield next;
184
+ const knownNonoccurringVariables = goal._knownNonoccurringVariables ?? null;
185
+ if (unify(goal.args[0], goal.args[1], next, { knownNonoccurringVariables })) yield next;
186
186
  }
187
187
  function* unificationWithOccursCheck({ goal, env }) {
188
188
  const next = env.clone();
package/src/solver.js CHANGED
@@ -804,7 +804,7 @@ export class Solver {
804
804
  attachBodyLocalFreshVariables(freshBody, localFreshPlan.body, freshVariables);
805
805
  const next = env.clone();
806
806
  this.stats.unify_calls++;
807
- if (!unify(goal, freshHead, next, { localFreshVariables: headLocalFresh })) continue;
807
+ if (!unify(goal, freshHead, next, { knownNonoccurringVariables: headLocalFresh })) continue;
808
808
  if (freshBody.length === 0) {
809
809
  yield* this.solve(rest, next, depth + 1);
810
810
  } else if (!groupNeedsActiveFrame(group)) {
@@ -1021,7 +1021,7 @@ function pushUserGoalUncachedFrames(stack, solver, group, goal, rest, env, depth
1021
1021
  attachBodyLocalFreshVariables(freshBody, localFreshPlan.body, freshVariables);
1022
1022
  const next = env.clone();
1023
1023
  solver.stats.unify_calls++;
1024
- if (!unify(goal, freshHead, next, { localFreshVariables: headLocalFresh })) continue;
1024
+ if (!unify(goal, freshHead, next, { knownNonoccurringVariables: headLocalFresh })) continue;
1025
1025
  if (freshBody.length === 0) {
1026
1026
  frames.push({
1027
1027
  kind: 'goals',
@@ -1083,10 +1083,10 @@ function freshVariableSet(names, freshVariables) {
1083
1083
 
1084
1084
  function attachBodyLocalFreshVariables(freshBody, plan, freshVariables) {
1085
1085
  for (let index = 0; index < freshBody.length; index++) {
1086
- const localFreshVariables = freshVariableSet(plan[index] ?? [], freshVariables);
1087
- if (localFreshVariables != null && freshBody[index]?.type === COMPOUND &&
1086
+ const knownNonoccurringVariables = freshVariableSet(plan[index] ?? [], freshVariables);
1087
+ if (knownNonoccurringVariables != null && freshBody[index]?.type === COMPOUND &&
1088
1088
  freshBody[index].name === '=' && freshBody[index].arity === 2) {
1089
- freshBody[index]._localFreshVariables = localFreshVariables;
1089
+ freshBody[index]._knownNonoccurringVariables = knownNonoccurringVariables;
1090
1090
  }
1091
1091
  }
1092
1092
  }
@@ -1257,12 +1257,11 @@ function* fixedLengthSolutions(solver, list, length, env) {
1257
1257
  const suffix = compactVariableList(remaining, `__length${id}_`);
1258
1258
  const next = env.clone();
1259
1259
  solver.stats.unify_calls++;
1260
- // cursor is dereferenced and the compact skeleton contains only freshly
1261
- // generated variables, so this binding cannot create a cycle. Binding it
1262
- // directly avoids traversing and expanding the new skeleton for an occurs
1263
- // check whose result is known by construction.
1264
- next.bind(cursor.name, suffix);
1265
- yield next;
1260
+ // The compact skeleton contains only freshly generated variables, so the
1261
+ // dereferenced tail variable is known not to occur in it. Reuse the same
1262
+ // proven-nonoccurrence path as source-level first-use unification.
1263
+ const knownNonoccurringVariables = new Set([cursor.name]);
1264
+ if (unify(cursor, suffix, next, { knownNonoccurringVariables })) yield next;
1266
1265
  }
1267
1266
 
1268
1267
  function* generatedLengthSolutions(solver, list, length, env) {
@@ -1296,15 +1295,13 @@ function* generatedLengthSolutions(solver, list, length, env) {
1296
1295
 
1297
1296
  const id = nextFreshId();
1298
1297
  let suffix = emptyList();
1298
+ // Every generated suffix is built from fresh variables and therefore cannot
1299
+ // contain the caller's dereferenced tail variable. Share the general
1300
+ // proven-nonoccurrence unification path instead of bypassing unify() here.
1301
+ const knownNonoccurringVariables = new Set([cursor.name]);
1299
1302
  for (let extra = 0n; ; extra++) {
1300
1303
  const next = env.clone();
1301
- // cursor is a dereferenced plain variable and suffix is made only from
1302
- // freshly generated variables, so this binding cannot create a cycle.
1303
- // Binding directly avoids an O(extra) occurs-check over the complete
1304
- // growing suffix for every answer; without this, unbounded length/2
1305
- // generation becomes quadratic and can spend hours before reaching the
1306
- // normal memory resource guard (issue #49).
1307
- next.bind(cursor.name, suffix);
1304
+ if (!unify(cursor, suffix, next, { knownNonoccurringVariables })) return;
1308
1305
  const answer = bindGeneratedLength(solver, length, count + extra, next);
1309
1306
  if (answer != null) yield answer;
1310
1307
  suffix = cons(variable(`__length${id}_${extra}`), suffix);
@@ -2395,7 +2392,7 @@ function selectReadyDeterministicBuiltin(goals, env, registry) {
2395
2392
  // A first-use proof is derived from source goal order. Do not move a later
2396
2393
  // deterministic builtin across that equality: doing so could touch one of
2397
2394
  // its proven-fresh variables before the checked binding executes.
2398
- if (goal?._localFreshVariables != null) return 0;
2395
+ if (goal?._knownNonoccurringVariables != null) return 0;
2399
2396
  if (goal.type !== COMPOUND && goal.type !== 'atom') continue;
2400
2397
  const def = registry.get(goal.name, goal.arity);
2401
2398
  if (!def?.deterministic || typeof def.ready !== 'function') continue;
package/src/term.js CHANGED
@@ -271,10 +271,11 @@ export function unify(left, right, env, options = {}) {
271
271
  // unification: a variable cannot be bound to a term containing itself.
272
272
  // Bindings are written into the supplied Env.
273
273
  const occursCheckHandler = options.occursCheck === 'fail' ? null : env?._occursCheckHandler;
274
- // A solver-proven first-use singleton variable cannot already occur in the
275
- // term it is about to receive. This internal proof lets that one binding
276
- // skip the occurs traversal without weakening ordinary unification.
277
- const localFreshVariables = options.localFreshVariables ?? null;
274
+ // Callers may provide a proof that selected variables cannot occur in the
275
+ // term they are about to receive. Source-level first-use analysis and a few
276
+ // construction fast paths share this internal proof; ordinary unification
277
+ // remains fully occurs-checked.
278
+ const knownNonoccurringVariables = options.knownNonoccurringVariables ?? null;
278
279
  const stack = [[left, right]];
279
280
  while (stack.length) {
280
281
  let [a, b] = stack.pop();
@@ -290,7 +291,7 @@ export function unify(left, right, env, options = {}) {
290
291
  continue;
291
292
  }
292
293
  if (a.type === VAR) {
293
- if (!localFreshVariables?.has(a.name) && occurs(a.name, b, env)) {
294
+ if (!knownNonoccurringVariables?.has(a.name) && occurs(a.name, b, env)) {
294
295
  occursCheckHandler?.(a, b, env);
295
296
  return false;
296
297
  }
@@ -299,7 +300,7 @@ export function unify(left, right, env, options = {}) {
299
300
  continue;
300
301
  }
301
302
  if (b.type === VAR) {
302
- if (!localFreshVariables?.has(b.name) && occurs(b.name, a, env)) {
303
+ if (!knownNonoccurringVariables?.has(b.name) && occurs(b.name, a, env)) {
303
304
  occursCheckHandler?.(b, a, env);
304
305
  return false;
305
306
  }
@@ -1318,7 +1318,7 @@ c4 ?- call((!;1)).
1318
1318
  },
1319
1319
  },
1320
1320
  {
1321
- name: 'first-use local equality shortcut preserves finite-tree occurs checking',
1321
+ name: 'proven-nonoccurrence first-use shortcut preserves finite-tree occurs checking',
1322
1322
  run: () => {
1323
1323
  const program = Program.parse(`
1324
1324
  first_use_cycle :- X = f(Y), Y = g(X).
@@ -1900,14 +1900,16 @@ unification, another list predicate, or answer readback inspects it. This is a
1900
1900
  storage optimization, not a distinct Prolog term or list semantics. Embedders
1901
1901
  that inspect the JavaScript term model can recognize this representation with
1902
1902
  `CompactListTerm`, `isCompactList`, and `compactListLength`, or construct one
1903
- with `compactVariableList`. For open-ended `length(List, N)` generation, the
1904
- bundled path binds each fresh generated spine directly instead of re-running an
1905
- occurs-check over the whole growing list. It also reserves recovery headroom
1906
- proportional to the retained spine, so a finite heap limit is raised inside the
1907
- `length/2` search as a catchable `resource_error(memory)` rather than allowing
1908
- an outer solver frame to encounter the limit first.
1909
-
1910
- The same principle now has a conservative general form for freshly renamed
1903
+ with `compactVariableList`. For open-ended `length(List, N)` generation, each generated spine is known not
1904
+ to contain the caller's dereferenced tail variable. The bundled path passes that
1905
+ proof through the normal unifier, sharing the same proven-nonoccurrence mechanism
1906
+ as first-use clause variables instead of maintaining a predicate-specific raw
1907
+ binding shortcut. It still reserves recovery headroom proportional to the
1908
+ retained spine, so a finite heap limit is raised inside the `length/2` search as
1909
+ a catchable `resource_error(memory)` rather than allowing an outer solver frame
1910
+ to encounter the limit first.
1911
+
1912
+ The same proven-nonoccurrence mechanism has a conservative source-level form for freshly renamed
1911
1913
  clauses. A singleton variable in the clause head, or a variable that has not
1912
1914
  appeared in the head or any earlier body goal and occurs exactly once in a
1913
1915
  direct `=/2` goal, cannot already be a subterm of the value it is about to
@@ -6121,47 +6123,74 @@ between solution branches.
6121
6123
 
6122
6124
  #### Interoperability profile and conservative autoloading
6123
6125
 
6124
- The full EyeProlog library is larger than the deliberately conservative
6125
- EyeProlog/Trealla/Scryer source-interoperability profile. The two concepts are
6126
- kept separate: a predicate can be implemented as ordinary portable Prolog and
6127
- still have an API that is not shared by the other systems.
6128
-
6129
- `library(lists)` is the first aligned common module. Its interop surface includes
6130
- `member/2`, `memberchk/2`, `select/3`, `append/2-3`, `last/2`,
6131
- `same_length/2`, `nth0/3-4`, `nth1/3-4`, `reverse/2`, `length/2`,
6132
- `maplist/2-8`, `foldl/4-6`, `sum_list/2`, and `list_to_set/2`. Its `length/2`
6133
- is fully relational: when both arguments are variables, `length(Xs, N)`
6134
- enumerates `Xs = [], N = 0`, then one-element lists with `N = 1`, and so on.
6135
- This generator mode is important for portable stress and enumeration programs.
6136
- EyeProlog-only helpers such as `set_nth0/4`, `take/3`, `drop/3`, and `slice/4`
6137
- remain available for compatibility but are outside this profile.
6138
-
6139
- `library(iso_ext)` is also recognized as a common interop module name.
6140
- EyeProlog exports `call_nth/2` there, matching Scryer's explicit import
6141
- organization while still permitting unqualified autoloaded source. The aligned
6142
- `library(lists)` and `library(iso_ext)` export sets are intentionally disjoint,
6143
- so a source file may import both without an accidental predicate collision.
6144
- `library(prologue)` is retained as an EyeProlog compatibility umbrella and may
6145
- overlap these modules; use selective imports when legacy code combines it with
6146
- the aligned libraries.
6147
-
6148
- `library(lambda)` is an explicitly imported Scryer-aligned module for
6149
- higher-order programming. Its implementation is adapted from Ulrich Neumerkel's
6150
- `library(lambda)` as distributed by Scryer Prolog, retaining the upstream
6151
- copyright and redistribution notice. The public syntax is:
6126
+ EyeProlog keeps four related concepts separate:
6127
+
6128
+ | Layer | Meaning |
6129
+ | --- | --- |
6130
+ | **ISO core** | The documented ISO predicate profile built into the processor. No EyeProlog library import is involved. |
6131
+ | **EyeProlog library surface** | Every module and exported predicate listed in the catalog above. Programs normally access these with `use_module/1-2`. |
6132
+ | **Interoperability profile** | A deliberately smaller set of library names and predicate interfaces that EyeProlog intends to keep source-compatible with Trealla and Scryer where practical. |
6133
+ | **Autoload map** | An even smaller convenience table that gives selected unqualified predicates one canonical EyeProlog provider. |
6134
+
6135
+ These layers answer different questions. A predicate may be implemented entirely
6136
+ as ordinary Prolog and still be outside the cross-processor interoperability
6137
+ profile; conversely, an interoperable predicate may be backed by a private host
6138
+ adapter. In this section, **portable** refers to source portability between
6139
+ Prolog systems, not merely to the language in which a predicate happens to be
6140
+ implemented.
6141
+
6142
+ The current interoperability profile recognizes these library roles:
6143
+
6144
+ | Library | Role in the interoperability profile |
6145
+ | --- | --- |
6146
+ | `library(lists)` | Common list module. A conservative subset of its exports is in the shared predicate profile. |
6147
+ | `library(iso_ext)` | Common extension-module name. `call_nth/2` is currently its autoloaded cross-engine predicate. |
6148
+ | `library(lambda)` | Scryer-aligned higher-order notation. It is imported explicitly because loading it also installs the `+\` operator. |
6149
+ | `library(prologue)` | EyeProlog compatibility module, not a common interop library name. `between/3` is nevertheless autoloaded from it so portable source need not name this EyeProlog-specific provider. |
6150
+
6151
+ Other modules from the catalog, including `library(clpz)`, `library(strings)`,
6152
+ `library(random)`, and `library(uuid)`, remain normal EyeProlog libraries. They
6153
+ can be imported explicitly, but their module names and full APIs are not thereby
6154
+ claimed as part of the current conservative Trealla/Scryer profile.
6155
+
6156
+ For `library(lists)`, the current interop predicate set is `member/2`,
6157
+ `memberchk/2`, `select/3`, `append/2-3`, `last/2`, `same_length/2`,
6158
+ `nth0/3-4`, `nth1/3-4`, `reverse/2`, `length/2`, `maplist/2-8`,
6159
+ `foldl/4-6`, `sum_list/2`, and `list_to_set/2`. Other exports from the same
6160
+ module, such as `min_list/2`, `max_list/2`, `set_nth0/4`, `take/3`, `drop/3`,
6161
+ and `slice/4`, remain available to EyeProlog programs but lie outside this
6162
+ conservative cross-engine subset.
6163
+
6164
+ `length/2` remains fully relational. With both arguments variable,
6165
+ `length(Xs, N)` enumerates `Xs = [], N = 0`, then one-element lists with
6166
+ `N = 1`, and so on. Open-ended generation uses the normal memory guard with
6167
+ recovery headroom so finite-heap exhaustion remains a catchable
6168
+ `resource_error(memory)`.
6169
+
6170
+ `library(iso_ext)` is a common interop module name, but only part of its
6171
+ EyeProlog API belongs to the shared profile. `call_nth/2` is mapped there to
6172
+ match Scryer's explicit import organization while also permitting portable
6173
+ unqualified source to autoload it. The interop exports of `library(lists)` and
6174
+ `library(iso_ext)` are kept disjoint, so both modules can be imported together
6175
+ without an accidental collision. `library(prologue)` remains a compatibility
6176
+ umbrella and overlaps them; use selective imports when legacy code combines it
6177
+ with the aligned modules.
6178
+
6179
+ `library(lambda)` follows Scryer's higher-order notation, adapted from Ulrich
6180
+ Neumerkel's permissively licensed implementation. Its public syntax is:
6152
6181
 
6153
6182
  ```text
6154
6183
  \X1^X2^...^XN^Goal
6155
6184
  Free+\X1^X2^...^XN^Goal
6156
6185
  ```
6157
6186
 
6158
- The first form has no explicitly shared free variables. Before each invocation
6159
- EyeProlog copies the closure term, so local variables are fresh on successive
6187
+ The first form has no explicitly shared free variables. Before each invocation,
6188
+ EyeProlog copies the closure term so local variables are fresh on successive
6160
6189
  `maplist/2-8`, `foldl/4-6`, or direct `call/N` uses. In the second form, the
6161
- variables contained in `Free` remain shared with the surrounding goal. Importing
6162
- the library installs `+\` as a priority-201 `xfx` operator; `\` and `^` use
6163
- their existing ISO operator definitions. Parenthesize lower-priority goal
6164
- operators after `^`, for example `\X^(X > 3)`.
6190
+ variables contained in `Free` remain shared with the surrounding goal.
6191
+ Importing the library installs `+\` as a priority-201 `xfx` operator; `\` and
6192
+ `^` use their existing ISO operator definitions. Parenthesize lower-priority
6193
+ goal operators after `^`, for example `\X^(X > 3)`.
6165
6194
 
6166
6195
  A continuation lambda may leave arguments for a later call:
6167
6196
 
@@ -6171,56 +6200,78 @@ f(x, y).
6171
6200
  answer(A, B) :- call(\X^f(X), A, B).
6172
6201
  ```
6173
6202
 
6174
- This is equivalent to supplying both arguments directly. A lambda that is
6175
- called with too few parameters raises `existence_error(lambda_parameter, ...)`,
6176
- matching the diagnostic intent of the Scryer library. EyeProlog uses its ISO
6177
- `copy_term/2` implementation for the fresh-copy step; in EyeProlog this gives
6178
- the natural-copy behavior needed by the library without requiring a separate
6179
- `copy_term_nat/2` predicate.
6180
-
6181
- Normal EyeProlog execution can autoload an otherwise undefined unqualified call
6182
- only when the interop table assigns it one canonical provider. Thus `member/2`
6183
- autoloads from `library(lists)`, `call_nth/2` from `library(iso_ext)`, and
6184
- `between/3` from the internal `library(prologue)` implementation. Autoloading is
6185
- disabled by `--no-autoload`, by the JavaScript option `autoload: false`, and
6186
- always by `--iso-strict`.
6187
-
6188
- `-w` / `--warnings` reports explicit non-profile library dependencies and calls
6189
- to non-profile predicates from common libraries. `--portable` turns those
6190
- portability diagnostics into a failing command, making the profile suitable for
6191
- continuous integration. `npm run test:interop` executes the same Sudoku source
6192
- under EyeProlog, Trealla, and Scryer when those commands are installed; the
6193
- repository's interoperability workflow installs them and runs that check.
6194
-
6195
- `library(clpz)` follows the Trealla and Scryer convention for constraint logic
6196
- programming over integers. Its first implementation step provides finite
6203
+ This is equivalent to supplying both arguments directly. A lambda called with
6204
+ too few parameters raises `existence_error(lambda_parameter, ...)`. EyeProlog
6205
+ uses its ISO `copy_term/2` implementation for the fresh-copy step and does not
6206
+ require a separate `copy_term_nat/2` predicate.
6207
+
6208
+ Autoloading is a convenience layered on top of the interoperability profile; it
6209
+ is not a general search through all EyeProlog libraries. During normal
6210
+ execution, an otherwise undefined **unqualified** predicate may be autoloaded
6211
+ only when the interop table assigns it one canonical provider. For example:
6212
+
6213
+ | Predicate | Canonical autoload provider |
6214
+ | --- | --- |
6215
+ | `member/2` | `library(lists)` |
6216
+ | `call_nth/2` | `library(iso_ext)` |
6217
+ | `between/3` | `library(prologue)` |
6218
+
6219
+ Predicates outside that table require an explicit import even when EyeProlog
6220
+ provides them. Explicit imports therefore remain the clearest way to state
6221
+ library dependencies:
6222
+
6223
+ ```text
6224
+ :- use_module(library(lists)).
6225
+ :- use_module(library(iso_ext), [call_nth/2]).
6226
+ ```
6227
+
6228
+ Use `--no-autoload`, or the JavaScript option `autoload: false`, when every
6229
+ library dependency should be explicit. `--iso-strict` always disables EyeProlog
6230
+ library autoloading.
6231
+
6232
+ `-w` / `--warnings` reports explicit dependencies on non-profile libraries and
6233
+ calls to non-profile predicates from otherwise common modules. `--portable`
6234
+ turns those diagnostics into a failing run, making the conservative profile
6235
+ suitable for continuous integration. `npm run test:interop` executes the same
6236
+ portable Sudoku source under EyeProlog, Trealla, and Scryer when those commands
6237
+ are installed; the repository interoperability workflow installs them and runs
6238
+ that check.
6239
+
6240
+ #### Library notes beyond the interoperability profile
6241
+
6242
+ The catalog above is authoritative for the complete EyeProlog library surface.
6243
+ The following notes describe useful parts of that surface without extending the
6244
+ cross-engine claims made above.
6245
+
6246
+ `library(clpz)` follows the familiar Trealla and Scryer convention for
6247
+ constraint logic programming over integers. EyeProlog currently provides finite
6197
6248
  interval and union domains, arithmetic and reified constraints, backtrackable
6198
- labeling with `ff`, `up`, and `down`, global distinctness, linear sums and
6199
- scalar products, chains, elements, value counting, extensional tuple tables,
6249
+ labeling with `ff`, `up`, and `down`, global distinctness, linear sums and scalar
6250
+ products, chains, elements, value counting, extensional tuple tables,
6200
6251
  lexicographic chains, serialized schedules, global cardinality with costs,
6201
6252
  Hamiltonian circuits, three-way comparison, and domain reflection. Constraints
6202
6253
  are kept in the logical environment, so failed alternatives cannot leak domains
6203
- into later branches. The full Trealla library also contains automata,
6204
- cumulative and two-dimensional scheduling constraints, and unbounded-domain
6205
- propagation that are not yet exported here.
6206
-
6207
- `library(iso_ext)` collects extensions commonly found in mature Prolog
6208
- systems. `call_nth/2` exposes the ordinal number of each solution and supports
6209
- the explicit import used by Scryer-style source; `forall/2` checks an action for
6210
- every solution of a condition; `cfor/3` enumerates an inclusive evaluated
6211
- integer range; `succ/2` relates adjacent nonnegative integers; `findall/4`
6212
- collects into a difference list; and `variant/2` recognizes terms equal up to
6213
- variable renaming. Its portable `countall/2` counts solutions without exposing
6214
- a template. `countall/2` is not exported by `library(lists)`, avoiding a
6215
- full-module import collision between the two libraries.
6216
-
6217
- `uuid(+Seed0,-UUID,-Seed)` creates a version 4 UUID atom using `random/3`.
6218
- Passing the returned seed to the next call produces the next UUID; restarting
6219
- with the same integer seed reproduces the same sequence exactly. This explicit
6220
- state replaces hidden host entropy and behaves identically in Node and the
6221
- browser playground.
6222
-
6223
- On the command line, a program imports the modules it uses:
6254
+ into later branches. Trealla's larger library also contains facilities such as
6255
+ automata, cumulative and two-dimensional scheduling constraints, and
6256
+ unbounded-domain propagation that EyeProlog does not currently export.
6257
+
6258
+ Beyond its interop entry for `call_nth/2`, `library(iso_ext)` also exports
6259
+ EyeProlog's extension relations `countall/2`, `forall/2`, `succ/2`, `cfor/3`,
6260
+ `findall/4`, and `variant/2`. `forall/2` checks an action for every solution of a
6261
+ condition; `cfor/3` enumerates an inclusive evaluated integer range; `succ/2`
6262
+ relates adjacent nonnegative integers; `findall/4` collects into a difference
6263
+ list; and `variant/2` recognizes terms equal up to variable renaming.
6264
+ `countall/2` counts solutions without exposing a template. These exports do not
6265
+ all belong to the conservative interop subset merely because they share the
6266
+ `iso_ext` module.
6267
+
6268
+ `uuid(+Seed0,-UUID,-Seed)` from `library(uuid)` creates a version 4 UUID atom
6269
+ using `random/3`. Passing the returned seed to the next call produces the next
6270
+ UUID; restarting with the same integer seed reproduces the same sequence. This
6271
+ explicit state replaces hidden host entropy and behaves identically in Node and
6272
+ the browser playground.
6273
+
6274
+ On the command line, a program can state its library dependencies explicitly:
6224
6275
 
6225
6276
  ```sh
6226
6277
  printf '%s\n' ':- use_module(library(lists)).' 'answer(X) :- member(X, [ready]).' > program.pl
@@ -6228,12 +6279,13 @@ eyeprolog --goal 'answer(X)' program.pl
6228
6279
  eyeprolog -p program.pl # add proof output
6229
6280
  ```
6230
6281
 
6231
- JavaScript uses the same registry by default:
6282
+ JavaScript uses the same normal EyeProlog library registry by default:
6232
6283
 
6233
6284
  ```js
6234
6285
  import { run } from 'eyeprolog';
6235
6286
 
6236
6287
  const source = `
6288
+ :- use_module(library(lists)).
6237
6289
  answer(Whole) :- append([red, green], [blue], Whole).
6238
6290
  `;
6239
6291
 
@@ -6241,7 +6293,7 @@ const result = run(source, { goal: 'answer(X)' });
6241
6293
  console.log(result.stdout);
6242
6294
  ```
6243
6295
 
6244
- The mode notation below is descriptive:
6296
+ The mode notation used in the reference tables below is descriptive:
6245
6297
 
6246
6298
  - `+` means the argument must already have the required input shape;
6247
6299
  - `-` means the predicate produces that argument;
@@ -6250,9 +6302,9 @@ The mode notation below is descriptive:
6250
6302
  Most EyeProlog library predicates are projections or filters. When an input is
6251
6303
  unbound, malformed, outside its domain, or incompatible with the requested
6252
6304
  output, they normally **fail** rather than raising the ISO errors described in
6253
- the errors section above. They do not invent open-ended domains. Bind arithmetic operands, source
6254
- text, proper lists, indexes, dates, and aggregate generators before calling
6255
- the corresponding predicate.
6305
+ the errors section above. They do not invent open-ended domains. Bind arithmetic
6306
+ operands, source text, proper lists, indexes, dates, and aggregate generators
6307
+ before calling the corresponding predicate.
6256
6308
 
6257
6309
  #### Portable numeric, comparison, and date relations
6258
6310