Skip to content

Packaging

Joshua Shinavier edited this page Jun 1, 2026 · 2 revisions

This page describes Hydra's packaging model: the types that organize Hydra programs into definitions, modules, and packages, and the metadata attached to each. It explains the model as a concept; for where this code physically lives, see Code organization, and for the kernel type breakdown see Implementation.

The packaging types are defined in Hydra/Sources/Kernel/Types/Packaging.hs, in the hydra.packaging namespace.

The model at a glance

Hydra organizes code as a three-level containment hierarchy:

  • A package is a named, independently versioned collection of modules.
  • A module is a named collection of definitions sharing a namespace.
  • A definition binds a name to a term, a type, or a primitive.
Package
 ├─ metadata, dependencies (on other packages)
 └─ Module*
     ├─ metadata, dependencies (on other modules)
     └─ Definition*  (term | type | primitive)
         └─ metadata

Every level — package, module, and definition — can carry the same optional EntityMetadata (documentation and lifecycle information). This uniformity is deliberate: see Why metadata is bundled.

Definitions

A Definition is the smallest named unit. It is a union of three cases:

  • A term definition (TermDefinition) binds a name to a term, with an optional type signature. When the signature is absent, it is inferred.
  • A type definition (TypeDefinition) binds a name to a type scheme.
  • A primitive definition (PrimitiveDefinition) declares a built-in function or constant: its name, an always-explicit signature, purity and totality flags, and an optional cross-compilable default implementation expressed as a Hydra term.

Each definition carries its own optional EntityMetadata.

From the packaging point of view, a primitive is simply one kind of definition, declared independently of any host language; for how primitives are added and implemented, see Adding a primitive.

Modules and namespaces

A Module is a logical collection of definitions in a single namespace. It has:

  • a name (a ModuleName, e.g. hydra.core), which is the common prefix for every definition name in the module;
  • optional metadata;
  • a list of dependencies on other modules;
  • the definitions themselves.

A module name is both an identity and a namespace prefix. A QualifiedName pairs an optional module name with a mandatory local name, which is how a definition within a module is referred to from elsewhere.

Module dependencies

A ModuleDependency names a depended-on module and, optionally, the package that provides it. When the package is omitted, the resolver searches all packages in scope. A module name that is ambiguous across packages is a resolution error, which can be disambiguated by naming the intended package explicitly.

Packages

A Package is a named collection of modules — the unit of distribution and versioning. It has a name (a PackageName, e.g. hydra-kernel), optional metadata, a list of package-level dependencies, and its modules.

Package dependencies and versions

A PackageDependency names another package and constrains it with a VersionSpecifier.

A Version is a version string such as "0.15" or "1.0.0".

The version specifier is intentionally minimal today: the only variant is any (any version satisfies the dependency). Further variants such as exact, caret, and range can be added later without breaking consumers of the any form — an instance of the forward-compatibility principle that runs throughout the packaging model.

Entity metadata

Any packaging entity — a package, a module, or a definition — may carry an optional EntityMetadata record. It bundles four independently optional kinds of information:

  • description — an optional, concise one-line human-readable summary of the entity.
  • comments — zero or more long-form prose notes: cross-cutting semantic conventions, caveats, and references that would otherwise be repeated across the entity's constituents. For example, a comment on the hydra.lib.chars module can state once that all of its primitives interpret their argument as a Unicode code point, rather than repeating that on every predicate.
  • seeAlso — typed cross-references to related entities, for navigation and documentation.
  • lifecycle — optional version-lifecycle milestones (see below).

Lifecycle and versioning

A LifecycleInfo records version milestones for an entity. Each milestone is independently optional:

  • availableSince — the Version in which the entity was introduced.
  • deprecatedSince — the Version in which the entity was deprecated, if applicable.

Further milestones (for example stableSince or removedSince) can be added later without changing the types that carry the lifecycle.

Cross-references

The seeAlso field holds a list of EntityReference values. An EntityReference is a typed pointer to a packaging entity: a package (by PackageName), a module (by ModuleName), or a definition.

A reference to a definition is a DefinitionReference, which names a type, a term, or a primitive by its Name. Because these references are typed rather than free-form strings, tools can resolve and validate them — for navigation, documentation, or impact analysis.

Why metadata is bundled

Earlier versions of the model attached documentation directly to each entity — for instance, a module carried a description field of its own. Adding any new kind of metadata then meant adding a parallel field to every entity type that should carry it, changing the shape of Module, Package, and each definition type in lockstep.

Bundling all such information behind a single optional metadata field of type EntityMetadata decouples what metadata exists from which entities carry it. New metadata — another comment category, a new lifecycle milestone, a new kind of cross-reference — is added inside EntityMetadata once, and every entity gains it without any change to its own field shape.

This is a backward-compatibility measure: it lets the packaging model grow without breaking the encoded shape of existing packages, modules, and definitions. The same principle motivates the open-ended VersionSpecifier and LifecycleInfo.

See also

Clone this wiki locally