This directory contains a complete Java implementation of Hydra. Hydra-Java passes all tests in the common test suite, ensuring identical behavior with Hydra-Haskell and Hydra-Python.
Hydra is a type-aware data transformation toolkit which aims to be highly flexible and portable. It has its roots in graph databases and type theory, and provides APIs in Haskell, Java, and Python. See the main Hydra README for more details.
JavaDocs for Hydra-Java can be found here, and releases can be found on Maven Central here.
Hydra-Java requires Java 17 or later. Build the project with Gradle:
./gradlew buildTo publish the resulting JAR to your local Maven repository:
./gradlew publishToMavenLocalYou may need to set the JAVA_HOME environment variable:
JAVA_HOME=/path/to/java17/installation ./gradlew buildFor comprehensive documentation about Hydra's architecture and usage, see:
- Concepts - Core concepts and type system
- Implementation - Implementation guide
- Code Organization - The src/main vs src/gen-main pattern
- Testing - Common test suite documentation
- Developer Recipes - Step-by-step guides
Hydra-Java has two types of tests: the common test suite (shared across all Hydra implementations) and Java-specific tests. See the Testing wiki page for comprehensive documentation.
The common test suite (hydra.test.testSuite) ensures parity across all Hydra implementations. Passing all common test suite cases is the criterion for a true Hydra implementation.
To run all tests:
./gradlew testThe test suite is generated from Hydra DSL sources and includes:
- Primitive function tests (lists, strings, math, etc.)
- Case conversion tests (camelCase, snake_case, etc.)
- Type inference tests
- Type checking tests
- Evaluation tests
- JSON coder tests
- Rewriting and hoisting tests
Java-specific tests validate implementation details and Java-specific functionality. These are located in src/test/java/ alongside the common test suite runner.
To run a specific test class:
./gradlew test --tests "hydra.VisitorTest"Hydra-Java uses the src/main vs src/gen-main separation pattern (see Code organization wiki page for details).
-
src/main/java/- Hand-written Java codehydra/lib/- Primitive function implementationshydra/util/- Core utilities (Either, Maybe, Tuple, Unit, Lazy, etc.)hydra/tools/- Framework classes (PrimitiveFunction, MapperBase, etc.)- Language-specific parsers and extensions
-
src/gen-main/java/- Generated Java codehydra/core/- Core types (Term, Type, Literal, etc.)hydra/graph/,hydra/module/- Graph and module structureshydra/coders/,hydra/compute/- Type adapters and computational abstractionshydra/reduction/,hydra/rewriting/,hydra/hoisting/- Term transformationshydra/inference/,hydra/checking/- Type inference and checking- Generated from Haskell DSL sources using the Java coder in hydra-ext
-
src/gen-test/java/- Generated test suitehydra/test/- Common tests ensuring parity with Haskell and Pythongeneration/- Generation tests (terms generated to Java and executed)
The Java code in src/gen-main/java and src/gen-test/java is generated from sources in Hydra's bootstrapping implementation, Hydra-Haskell.
See the Hydra-Haskell README for more information on how this works.
The recommended way to regenerate all Java code is to use the sync script:
cd ../hydra-ext
./bin/sync-java.shThis will:
- Generate the kernel modules
- Generate the eval lib modules
- Generate the kernel tests
- Generate the generation tests
- Build and run all tests
For manual generation, enter GHCi from hydra-ext:
cd ../hydra-ext && stack ghciAnd run the following commands in the GHC REPL:
-- Generate the kernel
writeJava "../hydra-java/src/gen-main/java" kernelModules kernelModules
-- Generate the test suite
let allModules = mainModules ++ testModules
writeJava "../hydra-java/src/gen-test/java" allModules baseTestModulesThe Hydra coder which generates everything in src/gen-main and src/gen-test can be found here.
A variety of techniques are used in order to materialize Hydra's core language in Java,
including a pattern for representing algebraic data types which was originally proposed by Gabriel Garcia, and used in Dragon.
For example, the generated Vertex class represents a property graph vertex, and corresponds to a record type:
public class Vertex<V> {
public final hydra.pg.model.VertexLabel label;
public final V id;
public final java.util.Map<hydra.pg.model.PropertyKey, V> properties;
public Vertex (hydra.pg.model.VertexLabel label, V id, java.util.Map<hydra.pg.model.PropertyKey, V> properties) {
java.util.Objects.requireNonNull((label));
java.util.Objects.requireNonNull((id));
java.util.Objects.requireNonNull((properties));
this.label = label;
this.id = id;
this.properties = properties;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Vertex)) {
return false;
}
Vertex o = (Vertex) (other);
return label.equals(o.label) && id.equals(o.id) && properties.equals(o.properties);
}
@Override
public int hashCode() {
return 2 * label.hashCode() + 3 * id.hashCode() + 5 * properties.hashCode();
}
// ... with* methods for immutable updates
}See Vertex.java for the complete class,
as well as the Vertex type in Pg/Model.hs for comparison.
Both files were generated from the property graph model defined here.
Union types (sum types) are represented using the visitor pattern. For example, the Element type is a tagged union of Vertex and Edge:
public abstract class Element<V> {
private Element () {}
public abstract <R> R accept(Visitor<V, R> visitor) ;
public interface Visitor<V, R> {
R visit(Vertex<V> instance) ;
R visit(Edge<V> instance) ;
}
public interface PartialVisitor<V, R> extends Visitor<V, R> {
default R otherwise(Element<V> instance) {
throw new IllegalStateException("Non-exhaustive patterns when matching: " + (instance));
}
default R visit(Vertex<V> instance) { return otherwise((instance)); }
default R visit(Edge<V> instance) { return otherwise((instance)); }
}
public static final class Vertex<V> extends hydra.pg.model.Element<V> {
public final hydra.pg.model.Vertex<V> value;
// ... constructor, equals, hashCode, accept
}
public static final class Edge<V> extends hydra.pg.model.Element<V> {
public final hydra.pg.model.Edge<V> value;
// ... constructor, equals, hashCode, accept
}
}See Element.java for the complete class.
The Visitor class is for pattern matching over the alternatives, and PartialVisitor is a convenient extension which allows supplying a default value for alternatives not matched explicitly.
The Rewriting and Reduction classes are good examples of pattern matching in action, and there are simpler examples in VisitorTest.java.