Skip to content

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.