java-adt: Create immutable algebraic data structures for Java.

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Warnings:

A simple tool to create immutable algebraic data structures and visitors for Java (such as abstract syntax trees). The input syntax is similar to Haskell data types, and they will be compiled to Java class hierarchies.


[Skip to Readme]

Properties

Versions 0.2016.11.28, 0.2018.11.4, 1.0.20231204, 1.0.20251105, 1.1, 1.1
Change log CHANGELOG.md
Dependencies array, base (>=4.11 && <5), filepath, pretty [details]
License LicenseRef-OtherLicense
Author Andreas Abel
Maintainer Andreas Abel <andreas.abel@gu.se>
Category Java
Home page https://github.com/andreasabel/java-adt
Bug tracker https://github.com/andreasabel/java-adt/issues
Source repo head: git clone https://github.com/andreasabel/java-adt.git
Uploaded by AndreasAbel at 2026-01-12T14:28:40Z

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for java-adt-1.1

[back to package description]

java-adt

A tool to create immutable algebraic data structures and visitors for Java (such as abstract syntax trees). The input syntax is similar to Haskell data types, and they will be compiled to Java class hierarchies.

Since version 1.1 it can also output Java record syntax intended to be matched on in switch statements. Visitors for this syntax are obsolete and thus not generated.

Installation

With a running Haskell installation, simply type into your shell

cabal install java-adt

and make sure your .cabal/bin/ (or similar) is part of your system PATH.

Example 1: Immutable linked lists with default visitor

Input: List.hs

data List A = Nil | Cons { head :: A, tail :: List A }

Invocation java-adt List.hs prints to standard output:

abstract class List<A> {
}

class Nil<A> extends List<A> {
    public Nil () {
    }
}

class Cons<A> extends List<A> {
    public A head;
    public List<A> tail;
    public Cons (A head, List<A> tail) {
        this.head = head;
        this.tail = tail;
    }
}

Invocation: java-adt -o List.java List.hs leaves output in List.java.

Invocation: java-adt -d List.hs outputs same but with default visitor on standard output:

abstract class List<A> {
    public abstract <R> R accept (ListVisitor<R,A> v);
}

class Nil<A> extends List<A> {
    public Nil () {
    }
    public <R> R accept (ListVisitor<R,A> v) {
        return v.visit (this);
    }
}

class Cons<A> extends List<A> {
    public A head;
    public List<A> tail;
    public Cons (A head, List<A> tail) {
        this.head = head;
        this.tail = tail;
    }
    public <R> R accept (ListVisitor<R,A> v) {
        return v.visit (this);
    }
}

interface ListVisitor<R,A> {
    public R visit (Nil<A> l);
    public R visit (Cons<A> l);
}

Example 2: A simple AST with custom visitor

Input file Exp.hs: (Note the use of Haskell lists in [Exp])

data Exp
  = EInt  { i :: Integer }
  | EAdd  { e1 :: Exp, e2 :: Exp }
  | ECall { f :: String, es :: [Exp] }
--visitor Integer EvalVisitor

Invocation java-ast -o Exp.java Exp.hs outputs into Exp.java:

import java.util.List;

abstract class Exp {
    public abstract Integer accept (EvalVisitor v);
}

class EInt extends Exp {
    public Integer i;
    public EInt (Integer i) {
        this.i = i;
    }
    public Integer accept (EvalVisitor v) {
        return v.visit (this);
    }
}

class EAdd extends Exp {
    public Exp e1;
    public Exp e2;
    public EAdd (Exp e1, Exp e2) {
        this.e1 = e1;
        this.e2 = e2;
    }
    public Integer accept (EvalVisitor v) {
        return v.visit (this);
    }
}

class ECall extends Exp {
    public String f;
    public List<Exp> es;
    public ECall (String f, List<Exp> es) {
        this.f = f;
        this.es = es;
    }
    public Integer accept (EvalVisitor v) {
        return v.visit (this);
    }
}

interface EvalVisitor {
    public Integer visit (EInt e);
    public Integer visit (EAdd e);
    public Integer visit (ECall e);
}

Example 3: ASTs in record style for simple language

Input file CMM.hs.

data Ty = TInt | TDouble

data Exp
  = EId  { x :: String }
  | EAdd { e1 :: Exp, e2 :: Exp }

data Stm
  = SExp   { e :: Exp }
  | SIf    { e :: Exp, s1 :: Stm, s2 :: Stm }
  | SBlock { ss :: [Stm] }

Output generated by java-adt --record CMM.hs:

public interface CMM {

    enum Ty { TInt, TDouble };

    sealed interface Exp permits
        EId, EAdd {}

    record EId(String x) implements Exp {}
    record EAdd(Exp e1, Exp e2) implements Exp {}

    sealed interface Stm permits
        SExp, SIf, SBlock {}

    record SExp(Exp e) implements Stm {}
    record SIf(Exp e, Stm s1, Stm s2) implements Stm {}
    record SBlock(List<Stm> ss) implements Stm {}

}

Input file grammar

The input file format is similar to Haskell data type declarations, with the special comment --visitor.

  datadecl    ::= 'data' uppername '=' constructors visitors

  constructor ::= uppername ['{' fieldlist '}']

  fieldlist   ::= fieldlist ',' field
                | field

  field       ::= lowername '::' type

  type        ::= type atom
                | atom

  atom        ::= name
                | '[' type ']'
                | '(' type ')'

  visitor     ::= '--visitor' type name

Limitations