This guide explains Hydra's domain-specific language (DSL) utilities for constructing types and terms in Java.
Status note (0.17+). Two namespaces matter, and the boundary is architectural:
hydra.overlay.java.*is hand-written host-native code;hydra.*(e.g.hydra.dsl.lib.*) is generated. The hand-written direct + phantom DSLs live underhydra.overlay.java.dsl.*: the direct DSLshydra.overlay.java.dsl.{Types,Terms,Literals,LiteralTypes}, the phantom-typed DSLhydra.overlay.java.dsl.meta.Phantoms(+hydra.overlay.java.dsl.meta.Defsand the helper layerhydra.overlay.java.dsl.Helpers). These are the foundation for the host-native Java coder sources atpackages/hydra-java/src/main/java/hydra/sources/(post-#344). The library wrappers are the generated moduleshydra.dsl.lib.*(Lists,Maps,Sets,Logic,Math_,Optionals,Strings,Literals,Eithers,Pairs,Equality, …), imported directly. The hand-written meta-level domain DSLs once sketched here (ahydra.dsl.meta.Core/Graph/Computeaccessor layer) were never built and have been removed from this guide. Note this is distinct from the generated per-record constructor DSLshydra.dsl.Core/hydra.dsl.Graph/… (e.g.hydra.dsl.Core.lambda(...)), which do exist and are covered under "Accessing kernel-type fields" below. For typed field access in the coder sources, authors usePhantomsprojections (proj(...)) +Helpers; for primitive calls, the generatedhydra.dsl.lib.*. For full kernel-source authoring, use the Haskell DSL (DSL Guide (Haskell)); the Java DSL covers Java-coder authoring only.
Note: Hydra provides DSLs in all five implementation languages (Haskell, Java, Python, Scala, and Lisp). This guide focuses on the Java DSLs. For the comprehensive Haskell DSL guide (including kernel development context), see DSL Guide (Haskell). For the Python DSLs, see DSL Guide (Python). For the Scala DSLs, see DSL Guide (Scala).
Before using the DSL utilities, you should:
- Understand Hydra's core concepts: Concepts
- Know basic Java syntax
- Have built Hydra-Java locally (see Hydra-Java README)
- Overview
- The DSL variants
- When to use each variant
- Direct DSLs (Types and Terms)
- Phantom-typed DSL
- Library wrappers
- Type definitions
- Term definitions
- Common patterns
- Working with generated code
- Error handling
- Examples in the codebase
Hydra-Java provides a layered DSL system for working with Hydra types and terms:
| Layer | Module | Purpose |
|---|---|---|
| Direct DSLs | hydra.overlay.java.dsl.Types, hydra.overlay.java.dsl.Terms |
Raw construction of Type and Term instances |
| Phantom-typed DSL | hydra.overlay.java.dsl.meta.Phantoms |
TypedTerm<A> term construction; the phantom A documents intent (not a load-bearing check — see below) |
| Definition + helper layer | hydra.overlay.java.dsl.meta.Defs, hydra.overlay.java.dsl.Helpers |
Fluent define(NS,"name").doc(…).lam(…).to(…) builder + ref; typeref/typeDef/doc for assembling modules |
| Library wrappers | hydra.dsl.lib.* (generated) |
Typed wrappers around Hydra primitives (lists, sets, maps, etc.) |
| Term references | hydra.dsl.Strip, hydra.dsl.Serialization, ... (generated) |
Typed, rename-safe references to kernel functions (#467) |
The Direct DSLs are suitable for casual use: constructing test fixtures, prototyping, or building types.
The Phantom-typed DSL plus the definition/helper layer are used for writing Hydra kernel source code in
Java, mirroring the Haskell DSLs used in packages/hydra-haskell/src/main/haskell/Hydra/Sources/. The
hydra.dsl.lib.* wrappers are generated (not hand-written) and imported directly.
Module: hydra.overlay.java.dsl.Types
Constructs Type instances directly. Used for defining Hydra data types (records, unions, wrappers).
import hydra.overlay.java.dsl.Types;
Type personType = Types.record(
Types.field("name", Types.string()),
Types.field("age", Types.int32()));Module: hydra.overlay.java.dsl.Terms
Constructs raw Term instances. Useful for test data and simple term construction.
import hydra.overlay.java.dsl.Terms;
Term person = Terms.record(new Name("Person"),
Terms.field("name", Terms.string("Alice")),
Terms.field("age", Terms.int32(30)));Module: hydra.overlay.java.dsl.meta.Phantoms
Wraps raw Term construction in a TypedTerm<A> whose phantom parameter A names the intended
Hydra type at the Java level.
On the phantom
A: it is documentation of intent, not a load-bearing type check. The DSL is a meta-program that manipulatesTermas data, so most builders are<A> TypedTerm<A>withAunconstrained (it surfaces as<?>/Objectat nearly every call site — see the manyTypedTerm<?>signatures inPhantoms). It gives near-zero compile-time safety today; treat it as a readable annotation and do not write code that depends onAbeing accurate. This is a deliberate stance (a usability review considered both makingAreal end-to-end and dropping it, and chose to leave the signatures as-is): the meta-program never needs it, and threading real Hydra types throughAwould be a large change for no functional gain.
import static hydra.overlay.java.dsl.meta.Phantoms.*;
TypedTerm<String> greeting = string("hello");
TypedTerm<Integer> age = int32(30);
TypedTerm<Object> identity = lambda("x", var("x"));For typed field access on kernel types, there is no separate hand-written accessor "domain DSL" — the
coder sources project with Phantoms (proj(...)) and the hydra.overlay.java.dsl.Helpers layer. (For
constructing kernel records there are the generated hydra.dsl.Core/Graph/… constructor DSLs — see
"Accessing kernel-type fields" below.) See the host-native sources at
packages/hydra-java/src/main/java/hydra/sources/ for concrete examples.
The generated hydra.dsl.lib.* modules provide typed wrappers around Hydra primitive functions,
so a primitive call reads as a normal method call rather than a raw apply(var("hydra.lib..."), ...).
import hydra.dsl.lib.Sets;
import hydra.dsl.lib.Lists;
TypedTerm<java.util.Set<R>> u = Sets.union(s1, s2);
TypedTerm<java.util.List<B>> ys = Lists.map(f, xs);These modules are generated (one per hydra.lib.* library) and imported directly; they are not
hand-written. See Library wrappers below for the full list.
The generated hydra.dsl.<Module> interfaces provide one typed, rename-safe reference per kernel
term definition (#467), derived from the definition's inferred signature.
They replace stringly-typed var("hydra....") references, which no rename catches and which fail
only at inference time.
import hydra.dsl.Strip;
import hydra.dsl.Serialization;
TypedTerm<Type> stripped = Strip.deannotateType(typ); // hydra.strip.deannotateType, rename-safeOne interface is generated per curated term module — the demand set covers the modules the coder
sources reference (Strip, Serialization, Annotations, Names, Formatting, Constants, and
more; curated via the kernel Manifest's dslTermModules).
Prefer these over inline var("hydra....") strings in new code.
| Scenario | Recommended DSLs | Why |
|---|---|---|
| Defining Hydra types | Direct Types DSL | Constructs Type instances for type modules |
| Simple term construction | Direct Terms DSL | Quick and straightforward |
| Writing kernel source code | Phantom-typed DSL + Helpers |
Type safety + module-assembly helpers |
| Field access on kernel types | Phantoms.proj(...) |
Typed projection onto a variable |
| Primitive function calls | Library wrappers | Sets.union(a, b) instead of raw apply(var(...), ...) |
Rule of thumb:
- Type modules (defining data types): Use
hydra.overlay.java.dsl.TypeswithTypes.record(),Types.union(),Types.wrap() - Term modules (defining functions): Use
import static hydra.overlay.java.dsl.meta.Phantoms.* - Quick prototyping: Use
hydra.overlay.java.dsl.Termsdirectly
import hydra.core.*;
import hydra.overlay.java.dsl.Types;
// Literal types
Type stringType = Types.string();
Type int32Type = Types.int32();
Type booleanType = Types.boolean_();
// Container types
Type stringList = Types.list(Types.string());
Type stringMap = Types.map(Types.string(), Types.int32());
Type maybeInt = Types.optional(Types.int32());
Type intSet = Types.set(Types.int32());
// Pair and either
Type pairType = Types.pair(Types.string(), Types.int32());
Type eitherType = Types.either_(Types.string(), Types.int32());
// Function type
Type fn = Types.function(Types.string(), Types.int32());
// Record type (anonymous)
Type person = Types.record(
Types.field("name", Types.string()),
Types.field("age", Types.int32()));
// Union type
Type shape = Types.union(
Types.field("circle", Types.float64()),
Types.field("rectangle", Types.pair(Types.float64(), Types.float64())));
// Wrapper type (newtype)
Type name = Types.wrap(Types.string());
// Type variable (forward reference)
Type selfRef = Types.variable("hydra.core.Term");
// Unit type
Type unit = Types.unit();import hydra.core.*;
import hydra.overlay.java.dsl.Terms;
// Literals
Term hello = Terms.string("hello");
Term answer = Terms.int32(42);
Term flag = Terms.boolean_(true);
// Lists
Term numbers = Terms.list(Terms.int32(1), Terms.int32(2), Terms.int32(3));
// Records
Term person = Terms.record(new Name("Person"),
Terms.field("name", Terms.string("Alice")),
Terms.field("age", Terms.int32(30)));
// Lambdas
Term identity = Terms.lambda("x", Terms.var("x"));
Term add = Terms.lambda("x", Terms.lambda("y",
Terms.apply(Terms.apply(Terms.primitive("hydra.lib.math.add"),
Terms.var("x")), Terms.var("y"))));
// Application
Term applied = Terms.apply(identity, Terms.int32(42));
// Optional values
Term justVal = Terms.just(Terms.int32(42));
Term nothingVal = Terms.nothing();
// Let bindings
Term letExpr = Terms.let_("x", Terms.int32(5), Terms.var("x"));
// Union injection
Term circle = Terms.inject("Shape", "circle", Terms.float64(3.14));
// Wrapped term (newtype)
Term name = Terms.wrap("hydra.core.Name", Terms.string("myName"));import hydra.core.*;
// Pattern match on a Term
String describe(Term term) {
return term.accept(new Term.PartialVisitor<String>() {
@Override
public String visit(Term.Literal instance) {
return "A literal value";
}
@Override
public String visit(Term.List instance) {
return "A list with " + instance.value.size() + " elements";
}
@Override
public String otherwise(Term instance) {
return "Some other term";
}
});
}The phantom-typed DSL is the core of Hydra's Java metaprogramming system.
It wraps raw Term values in TypedTerm<A> to provide compile-time type tracking.
import hydra.typed.TypedBinding;
import hydra.typed.TypedTerm;
import hydra.util.Maybe;
import static hydra.overlay.java.dsl.meta.Phantoms.*;TypedTerm<String> greeting = string("hello");
TypedTerm<Integer> age = int32(42);
TypedTerm<Boolean> flag = boolean_(true);
TypedTerm<Boolean> yes = true_();
TypedTerm<Boolean> no = false_();// Lambda (single parameter)
TypedTerm<Object> id = lambda("x", var("x"));
// Lambda (multiple parameters — curried)
// Primitive calls use the generated hydra.dsl.lib.* wrappers (see "Library wrappers")
TypedTerm<Object> add = lambdas(List.of("x", "y"),
Math_.add(var("x"), var("y")));
// Function application
TypedTerm<Object> result = apply(var("f"), int32(5));
// Composition
TypedTerm<Object> composed = compose(var("g"), var("f"));
// Constant function
TypedTerm<Object> alwaysTrue = constant(true_());
// Identity
TypedTerm<Object> id2 = identity();// Lists
TypedTerm<List<Integer>> nums = list(int32(1), int32(2), int32(3));
// Pairs
TypedTerm<Object> kv = pair(string("key"), int32(42));
// Optional values
TypedTerm<Object> some = just(int32(42));
TypedTerm<Object> none = nothing();
// Either
TypedTerm<Object> ok = right(int32(42));
TypedTerm<Object> err = left(string("error"));// Construct a record (requires type name + fields)
// Generated kernel classes emit a `TYPE_` constant plus one bare-name constant per field.
import hydra.core.AnnotatedTerm;
TypedTerm<Object> annotated = record(AnnotatedTerm.TYPE_,
field(AnnotatedTerm.BODY, var("body")),
field(AnnotatedTerm.ANNOTATION, var("ann")));import hydra.core.FloatType;
import hydra.core.Literal;
// Inject into a union type
TypedTerm<Object> f = inject(Literal.TYPE_, Literal.FLOAT,
var("floatValue"));
// Unit injection (for enum-like variants): the 2-arg inject overload supplies unit
TypedTerm<Object> f32 = inject(FloatType.TYPE_, FloatType.FLOAT32);import hydra.core.Term;
// match creates a case elimination (unapplied)
TypedTerm<Object> matcher = match(Term.TYPE_,
Maybe.just(var("default")), // default case
field(Term.LITERAL, // case: literal
lambda("lit", string("found a literal"))),
field(Term.VARIABLE, // case: variable
lambda("v", string("found a variable"))));
// cases applies the match to an argument
TypedTerm<Object> result = cases(Term.TYPE_, var("myTerm"),
Maybe.nothing(), // no default
field(Term.LITERAL,
lambda("lit", var("lit"))),
field(Term.VARIABLE,
lambda("v", var("v"))));// Single let binding
TypedTerm<Object> expr = let1("x", int32(5),
apply(var("add"), var("x")));
// Multiple let bindings
TypedTerm<Object> expr2 = lets(List.of(
field(new Name("x"), int32(5)),
field(new Name("y"), int32(10))),
apply(apply(var("add"), var("x")), var("y")));// Create a field accessor function
TypedTerm<Object> getBody = project(AnnotatedTerm.TYPE_, AnnotatedTerm.BODY);
// Apply it
TypedTerm<Object> body = apply(getBody, var("annotated"));The combined "project a field, then apply to a named variable" pattern
is so common that Phantoms provides a proj shortcut:
// Equivalent to: apply(project(AnnotatedTerm.TYPE_, AnnotatedTerm.BODY), var("annotated"))
TypedTerm<Object> body = proj(AnnotatedTerm.TYPE_, AnnotatedTerm.BODY, "annotated");Overloads accept String or Name for the type/field arguments, and
either a String variable name (which becomes var("...")) or a
TypedTerm<?> for the receiver. Prefer proj() in DSL source modules —
it's the idiomatic form.
If the field has a thunked type (e.g., unit -> T, used to defer
expression evaluation for benchmarking; see UniversalTestCase.actual),
the projection alone yields the thunk — not its forced value. Force
with an extra apply(..., unit()):
// field type is `unit -> string` — force the thunk
TypedTerm<Object> value = apply(
apply(
project("hydra.testing.UniversalTestCase", "actual"),
var("ucase")),
unit());Missing the outer apply(..., unit()) causes inference to fail with
cannot unify string with (unit → string) for every binding in the
containing module, since the inferencer processes them in a shared context.
// Wrap a value (create a newtype instance)
TypedTerm<Object> hydraName = wrap(Name.TYPE_, string("myName"));
// Unwrap function
TypedTerm<Object> unwrapper = unwrap(Name.TYPE_);Primitive calls go through the generated hydra.dsl.lib.* wrappers — there are no
primitive/primitive1/primitive2 helpers. Each wrapper method is typed and rename-safe:
import hydra.dsl.lib.Strings;
import hydra.dsl.lib.Math_; // math.* wrapper; escaped to avoid clashing with java.lang.Math
TypedTerm<Integer> len = Strings.length(var("s"));
TypedTerm<Integer> sum = Math_.add(var("x"), var("y"));If no wrapper exists yet for a primitive, reference it by name and apply directly:
TypedTerm<Object> sum = apply(var("hydra.lib.math.add"), var("x"), var("y"));// Attach documentation to a term
TypedTerm<Object> documented = doc("Adds two numbers", var("add"));There is no separate "domain DSL" of typed accessors in Java — author field access with Phantoms
projection. proj(typeName, fieldName, varName) is the idiomatic form (project-then-apply onto a
variable):
import static hydra.overlay.java.dsl.meta.Phantoms.*;
// Lambda.body of the term bound to "lam"
TypedTerm<Object> body = proj(Lambda.TYPE_, Lambda.BODY, "lam");
// AnnotatedTerm.annotation of the term bound to "at"
TypedTerm<Object> ann = proj(AnnotatedTerm.TYPE_, AnnotatedTerm.ANNOTATION, "at");For constructing kernel records, use the Terms.record(...) / Phantoms constructors directly, or the
generated hydra.dsl.* constructor DSLs (e.g. hydra.dsl.Core.lambda(...)). The host-native sources at
packages/hydra-java/src/main/java/hydra/sources/ are the canonical worked examples.
Generated Hydra types provide a TYPE_ constant (the type's Name) plus one bare-name constant
per field or variant (the field/variant's local Name):
// From hydra.core.Term (generated)
Term.TYPE_ // Name("hydra.core.Term")
Term.LITERAL // Name("literal")
Term.VARIABLE // Name("variable")
Term.APPLICATION // Name("application")
// ... etc.
// From hydra.core.Lambda (generated)
Lambda.TYPE_ // Name("hydra.core.Lambda")
Lambda.PARAMETER // Name("parameter")
Lambda.BODY // Name("body")Always use these constants rather than constructing Name instances manually.
This ensures correctness and enables refactoring.
Library wrappers provide phantom-typed interfaces to Hydra's primitive functions. They are
generated — one hydra.dsl.lib.<Library> module per hydra.lib.* library — so you import and call
them directly rather than hand-rolling raw primitive applications:
import hydra.dsl.lib.Sets;
import hydra.dsl.lib.Lists;
TypedTerm<java.util.Set<R>> u = Sets.union(s1, s2);
TypedTerm<Integer> n = Lists.length(xs);
TypedTerm<B> acc = Lists.foldl(f, init, xs);The generated modules are: Lists, Maps, Sets, Logic, Math_, Optionals, Strings,
Literals, Eithers, Pairs, Equality, Chars (plus any other hydra.lib.* library). Each method
name matches the primitive's local name; each is fully typed via TypedTerm<A>.
The underlying primitive-reference form is still available when you need the unapplied primitive as a
term: Terms.primitive("hydra.lib.sets.union") yields the Term.Variable for that primitive.
Type-level modules define Hydra data types using the Direct Types DSL.
Each type definition is a Binding (a name-term pair).
import hydra.core.*;
import hydra.overlay.java.dsl.Types;
public interface MyTypes {
String NS = "my.namespace";
static Binding define(String localName, Type type) {
return hydra.Annotations.typeElement(
new Name(NS + "." + localName), type);
}
// Forward references
Type _Person = Types.variable(NS + ".Person");
Type _Address = Types.variable(NS + ".Address");
// Type definitions
Binding person = define("Person",
Types.record(
Types.field("name", Types.string()),
Types.field("age", Types.int32()),
Types.field("address", _Address)));
Binding address = define("Address",
Types.record(
Types.field("street", Types.string()),
Types.field("city", Types.string())));
}Types in the same module reference each other through Types.variable():
// Forward reference to another type in this module
Type _Term = Types.variable("hydra.core.Term");
// Use it in a record field
Binding lambda = define("Lambda",
Types.record(
Types.field("parameter", _Name),
Types.field("body", _Term)));The examples/ directory is aspirational — the file does not yet exist. For a
real reference, see the host-native Java coder sources at
packages/hydra-java/src/main/java/hydra/sources/, which use the same Phantoms
idiom against the full Hydra kernel.
Term-level modules define Hydra functions using the Phantom-typed DSL.
Each function definition is a TypedBinding<A> (a phantom-typed name-term pair).
Definitions use the fluent builder — the blessed idiom across all Java coder sources:
def("name").doc("...").lam("x").lam("y").to(() -> body). It reads top-to-bottom (name, doc,
parameters, then body) instead of the inside-out def("name", () -> doc("...", lambda("x", ...)))
nesting. The two forms are exactly equivalent — .to(() -> body) composes doc(desc, lambda([params], body)), omitting the doc/lambda wrappers when none are given — but the fluent form is what to write.
The body passed to .to(() -> ...) stays lazy (a Supplier), so a definition may reference sibling
Def fields declared later in the class.
import hydra.typed.*;
import hydra.util.Maybe;
import static hydra.overlay.java.dsl.meta.Phantoms.*;
public class MyFunctions {
public static final ModuleName NS = new ModuleName("my.namespace");
// Flat form (still supported); the fluent def(String) below is preferred.
private static Def def(String localName, Supplier<TypedTerm<?>> body) {
return Defs.define(NS, localName, body);
}
// Fluent form: def("name").doc("...").lam("x").to(() -> body)
private static Defs.DefBuilder def(String localName) {
return Defs.define(NS, localName);
}
// Simple function: pattern match + extract body
public static final Def deannotateTerm = def("deannotateTerm")
.doc("Remove annotations from a term")
.lam("term")
.to(() ->
cases(Term.TYPE_, var("term"),
Maybe.just(var("term")), // default: return unchanged
field(Term.ANNOTATED,
lambda("at",
apply(var("deannotateTerm"),
annotatedTermBody(var("at")))))));
}In Java, interface-level fields can reference themselves (the JVM handles initialization order).
Use var("namespace.functionName") for qualified self-references:
// Recursive: apply same function to the body
apply(var("my.namespace.deannotateTerm"), annotatedTermBody(var("at")))The examples/ directory is aspirational — the file does not yet exist. The same
patterns (simple pattern matching, case branches, composition with projection,
let-bindings, nested pattern matching, sets/folds/binding-aware rewriting,
structural rewriting, traversal-order dispatching) appear throughout the
host-native Java coder sources at packages/hydra-java/src/main/java/hydra/sources/,
which serve as the live working examples.
Match on a union type, handle one variant, pass others through:
TypedTerm<Object> fn = lambda("term",
cases(Term.TYPE_, var("term"),
Maybe.just(var("term")), // default: identity
field(Term.ANNOTATED, // handle one case
lambda("at", annotatedTermBody(var("at"))))));Bind a local transform, pass it to a rewriting function:
TypedTerm<Object> fn = lambda("typ",
let1("f",
lambda("recurse", lambda("t",
cases(Type.TYPE_, var("t"),
Maybe.just(apply(var("recurse"), var("t"))),
field(Type.ANNOTATED,
lambda("at",
apply(var("recurse"),
annotatedTypeBody(var("at")))))))),
apply(apply(var("rewriteType"), var("f")), var("typ"))));Accumulate results over subterms:
TypedTerm<Object> vars = let1("dfltVars",
listsFoldl(
lambda("s", lambda("t",
setsUnion(var("s"),
apply(var("freeVariablesInTerm"), var("t"))))),
setsEmpty(),
apply(var("subterms"), var("term"))),
// then match on specific cases...
cases(Term.TYPE_, var("term"),
Maybe.just(var("dfltVars")),
// ...
));Check whether a variable is shadowed before rewriting:
TypedTerm<Object> replaceFn = lambda("recurse", lambda("t",
cases(Term.TYPE_, var("t"),
Maybe.just(apply(var("recurse"), var("t"))),
field(Term.LAMBDA,
lambda("l",
// Stop if lambda shadows our variable
apply(apply(var("ifElse"),
equalName(lambdaParameter(var("l")), var("name"))),
var("t"),
apply(var("recurse"), var("t"))))))));Generated Java classes for Hydra types provide:
- Visitor pattern for union types (
accept,Visitor<R>,PartialVisitor<R>) - Static name constants (
TYPE_, plus one bare-name constant per field/variant) - Serializable implementations
- Comparable implementations
- Fluent builders and copy-update methods for record types (see below)
// hydra.core.Term (generated)
public abstract class Term implements Serializable, Comparable<Term> {
public static final Name TYPE_ = new Name("hydra.core.Term");
public static final Name LITERAL = new Name("literal");
public static final Name VARIABLE = new Name("variable");
// ...
public static final class Literal extends Term { ... }
public static final class Variable extends Term { ... }
// ...
public abstract <R> R accept(Visitor<R> visitor);
}Every generated record type carries two native-Java affordances for construction, so applications do not have to call the all-args constructor directly or re-implement builder boilerplate.
Fluent builder.
Each record exposes a static builder() factory and a nested Builder class with one setter per
field (named after the field) and a build() that returns the immutable record:
import hydra.core.Binding;
Binding b = Binding.builder()
.name(new hydra.core.Name("x"))
.term(myTerm)
.typeScheme(hydra.util.Optional.empty())
.build();For generic records the type parameters are threaded through, so the builder stays type-safe:
// hydra.coders.Coder<V1, V2>
Coder<A, B> c = Coder.<A, B>builder()
.encode(myEncode)
.decode(myDecode)
.build(); // returns Coder<A, B>Copy-update methods.
Each record also has one withFieldName(...) method per field, returning a new instance with that one
field replaced and all others copied — useful for tweaking a single field of an immutable value:
Binding b2 = b.withName(new hydra.core.Name("y")); // same term + typeScheme, new nameNotes:
- Setters and copy-update methods are emitted for every generated record (no opt-in flag).
- A field whose name is a Java reserved word (e.g.
default,static,implements) gets a trailing underscore in its setter, matching the field/parameter escaping (e.g..default_(...)). - A field literally named
buildorbuilderwould collide with the generated methods and is likewise escaped tobuild_/builder_. - Builders do not null-check in
build(); Hydra-generated code never passes nulls, and user code is expected to supply every field (the all-args constructor remains available as well).
Hydra computations use Either<Error, A> for error handling (the former Flow monad
was removed in #245). An InferenceContext value is threaded alongside the graph
and carries the fresh-type-variable counter and the current subterm-path trace.
import hydra.overlay.java.util.Either;
import hydra.typing.InferenceContext;
import hydra.errors.Error;
import hydra.graph.Graph;
// Create a successful result
Either<Error, String> ok = Either.right("result");
// Map over a result
Either<Error, Integer> mapped =
hydra.lib.eithers.Map.apply(s -> s.length(), ok);
// Chain computations (bind / flatMap)
Either<Error, String> bound =
hydra.lib.eithers.Bind.apply(result1, value ->
Either.right(value + " processed"));
// Create a failure (Error is a tagged-union type; construct a variant from hydra.errors)
Either<Error, String> err = Either.left(Error.other("something went wrong"));
// Inspect a result
if (result.isRight()) {
String value = result.get();
} else {
Error failure = result.getLeft();
}All hand-written DSLs live under overlay/java/hydra-kernel/src/main/java/hydra/overlay/java/dsl/
(namespace hydra.overlay.java.dsl.*); the library wrappers are generated.
| File | Description |
|---|---|
overlay/java/hydra-kernel/src/main/java/hydra/overlay/java/dsl/meta/Phantoms.java |
Phantom-typed DSL (all operations) |
overlay/java/hydra-kernel/src/main/java/hydra/overlay/java/dsl/meta/Defs.java |
Module-definition helpers (define/ref/definitionsOf) |
overlay/java/hydra-kernel/src/main/java/hydra/overlay/java/dsl/Helpers.java |
typeref/typeDef/doc/typeScheme helpers |
overlay/java/hydra-kernel/src/main/java/hydra/overlay/java/dsl/Types.java |
Direct Types DSL |
overlay/java/hydra-kernel/src/main/java/hydra/overlay/java/dsl/Terms.java |
Direct Terms DSL |
hydra.dsl.lib.* (generated; e.g. hydra.dsl.lib.Lists/Maps/Sets/Logic/Math_/Optionals/Strings) |
Library wrappers — generated, imported directly |
hydra.dsl.* term references (generated; e.g. hydra.dsl.Strip/Serialization/Names) |
Typed, rename-safe references to kernel functions (#467) |
packages/hydra-java/src/main/java/hydra/sources/ |
Live host-native Java coder DSL sources (reference for current Phantoms idiom) |
- DSL Guide (Haskell) - Comprehensive DSL guide for kernel development
- DSL Guide (Python) - Python DSL guide
- DSL Guide (Scala) - Scala DSL guide
- Concepts - Core Hydra concepts
- Implementation - Implementation details and architecture
- Hydra-Java README - Getting started