HQL extensions and namespace initialization¶
Extensions provide capabilities outside the small hql/core. They may be implemented in Rust, but HQL users should interact with ordinary typed functions and values. Rust traits, lifetimes and implementation details do not become HQL language requirements.
The registry¶
A registry of extensions is implemented; a module loader is not. An extension
declares a name, a version and its steps, and each step supplies a structural
StepSpec, its evaluator, and whether it is pure — so a
capability is added without editing the core's checker or evaluator. Extensions
are compiled into the binary and registered in one table. There is no dynamic
loading and no plugin ABI: registration says what exists, and it is a host
mechanism that never reaches HQL's surface.
The signature declares generic parameters and bounds, input and result types,
argument names and arity, and lambda or type arguments. The checker and
hql builtins consume the same declaration. Ordinary steps need no custom
checker; dependent hooks add constraints such as intrinsic ordering or a
configured semantic index. Invalid signatures are rejected before checking a
program. Unknown, duplicate, and excess arguments are errors rather than ignored
syntax. Evaluators receive the checked execution tree, with resolved step and
type identities.
Registration is not import. Every extension in the binary is registered; a program resolves what it has imported. That is what lets a diagnostic name the import a program is missing instead of guessing at a misspelling.
Steps split by the layering core → graph → knowledge:
| Provider | Steps | How a program gets it |
|---|---|---|
| core | size contains get count sort take filter map typed |
always |
graph |
uplinks downlinks expand graph |
the prelude |
present |
table json text |
the prelude |
lexical |
lexical |
import lexical |
semantic |
semantic |
import semantic |
Collection operations stay in the core because they operate on the core's own collection types and mention nothing above them. Retrieval and drawing are not the core's business — see lexical, semantic, hql/semantic-search and hql/presentation.
import <extension> binds an extension's steps, and extension.step(…) is
always available for one that is imported. A name provided by two imported
extensions fails at the import, naming both. A vault states its own imports in
hql.toml, and may decline the prelude. The rules are in
the grammar and
the command line; the decision is
HQL-0001.
An extension declares each step Pure or Effectful, because pipes
reserves execution freedom that is only safe over pure steps. Every step
registered today is pure. HQL-0004 adds a
shape beside purity — elementwise, partition, reduction or barrier — and the
backend a checked program runs on. The map/reduce vocabulary and the backend
contract are one Rust module, mapreduce; its steps are the core's, and the
module is how the binary is organized, not something a program imports.
An extension may also contribute metadata while cards are assembled; lexical owns its own model and indexing entries. Extension-defined types and dynamically registered document sources are not implemented and need a decision of their own.
An HQL module organizes reusable HQL definitions. An extension connects HQL to external capabilities: for example, a data source, a renderer or an explicit persistence operation. These are candidate uses, not a bundled extension list. An extension may expose pure operations as well as effects.
Namespace initialization¶
Namespace initialization is unimplemented and independent of the registry
above: it needs module resolution, which the registry deliberately does not
provide. A vault or namespace may contain init.hql. Its intended role is to
import the modules and extensions available to that namespace. This gives the namespace
an explicit entry point for its evaluation environment.
Illustrative layout:
vault/
init.hql
alice.hmd
research/
init.hql
notes.hmd
Illustrative contents of init.hql, using candidate module-import syntax:
import people
import graph
The names represent available modules or extension interfaces; their discovery and installation are separate host responsibilities. Import syntax, exports, qualification and any distinction between module and extension imports remain open. An import is not implicitly a package download.
The research namespace can have its own imports. Whether it inherits the vault's
imports, how names shadow or conflict, initialization order, cycles, reloads and
failure handling all require decisions. The presence of nested init.hql files
alone does not settle those policies. Whether initialization permits work beyond
imports is also unresolved.
Environments and capabilities¶
Initialization establishes namespace availability. The bridge into card and cell environments still needs a precise contract. Cells close over their defining card environment, including explicit imports and bindings. Locals can shadow names; transclusion preserves the defining closure. Namespace initialization must remain compatible with those lexical rules. Card filenames never implicitly bind names.
The extension boundary should expose typed inputs, outputs and failures. It also needs a way to distinguish pure computation from effects so pipes can be optimized safely. Importing a capability and invoking an effect are separate operations. Reading, network access, printing and saving need explicit host contracts; importing a renderer does not itself render a value.
The host adapter supplies typed HMD documents using the existing parser/resolver. An extension should operate through that boundary rather than make raw text parsing part of the core. Native ABI versus another transport, registration, versioning, resource limits and capability policy remain open. This document does not choose a Rust plugin ABI or prescribe a trait hierarchy.
See architecture, cell environments and hql/design-status.