Scripts and Modules¶
Multi-language Support¶
zelph allows nodes to have names in multiple languages. This feature is particularly useful when integrating with external knowledge bases. The preferred language can be set in scripts using the .lang command:
.lang zelph
This capability is fully utilized in the Wikidata integration, where node names include both human-readable labels and Wikidata identifiers. An item in zelph can be assigned names in any number of languages, with Wikidata IDs being handled as a specific language ("wikidata").
Importing Scripts: Module IDs and Interchangeable Implementations¶
.import <script> loads and executes a zelph (.zph) or Janet (.janet)
script, resolving first against the working directory and then the standard
library (the .zph extension is optional).
Import once¶
Every imported .zph script is registered under a module ID — by default
its lowercase file name without extension (binary-arithmetic for
binary-arithmetic.zph). A script whose ID is already registered is skipped,
much like #pragma once in C++. Scripts can therefore declare their
prerequisites with plain .import lines at the top; shared dependencies are
never loaded twice, and import cycles terminate. .new clears the registry.
Janet scripts are exempt: they are runnable programs and may be executed
repeatedly, e.g. with different arguments.
The registry is session state and is deliberately not part of a .save
file — see Rules Say Themselves Only Once
for why re-importing a module after .load is both necessary and free.
Rules Say Themselves Only Once¶
Facts are hash-consed: the node is its structure, so asserting the same fact twice does nothing. A rule requires one additional step. It includes variables, variables are allocated fresh for each statement, and a node built from fresh variables is a fresh node – thus, entering the same rule twice would yield two rules deriving the same consequences at twice the unification cost.
zelph therefore recognises a rule it already has. A ... => ... statement
is compared against the existing rules up to a renaming of its
variables (alpha-equivalence, as in the lambda calculus); if one matches,
the newly built rule is rolled back and the statement evaluates to the rule
that was already there. What counts as "the same rule" is exactly what the
reasoner reads — set membership, the conjunction and negation tags, and each
condition's subject, predicate and objects — so two rules that survive the
check are guaranteed to behave differently.
This matters most in a place where it is easy to miss. .load restores the
graph but not the Janet side of a module (zelph/number, digit alphabets,
display schemes), so working with a saved arithmetic network means
re-importing the module:
.load math.bin
.import math # brings back &-literals -- and adds no second rule set
Without rule identity that sequence doubled every rule in the file.
Two boundaries: the check runs on parsed => statements, which covers
.import and the REPL, but not zelph/rule — a Janet program
building rules programmatically owns the nodes it passes in, and zelph does
not second-guess it. And a rule whose conditions mention a literal set
({...} in a term position) is never recognised as a duplicate, because
each such set is a fresh node; the outcome is the old behaviour, never a
wrong one.
Interchangeable implementations: .provides¶
A script can claim additional module IDs with
.provides <id> [<id2> ...]
placed at the top of the file. Scripts claiming the same ID are interchangeable — and mutually exclusive — implementations of one capability: whichever is imported first wins, and later providers of the ID are skipped.
The three arithmetic substrates all declare .provides arithmetic. Dependent
modules simply import the default substrate (.import binary-arithmetic); to
compute on a different substrate, import it before anything that depends
on arithmetic:
.import binary-nand-arithmetic # claims "arithmetic" first
.import symbolic-core # its ".import binary-arithmetic" is skipped
If a directly requested script is skipped because a different script already provides one of its IDs, zelph prints a warning — you asked for a specific implementation, but an alternative is already active.