MCPcopy Index your code
hub / github.com/derive4j/derive4j

github.com/derive4j/derive4j @v1.1.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.1.1 ↗ · + Follow
857 symbols 3,078 edges 100 files 9 documented · 1% 1 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Derive4J: Java 8 annotation processor for deriving algebraic data types constructors, pattern matching and more!

Travis codecov.io [Maven Central][search.maven] Gitter Chat

tl;dr Show me how to write, say, the Either sum type with Derive4J!.

Table of contents

Caution: if you are not familiar with Algebraic Data Types or the "visitor pattern" then you may want to learn a bit about them.

So, what can this project do for us, poor functional programmers stuck with a legacy language called Java? A good deal of what is commonly available in better languages like Haskell, including: - structural pattern matching on Algebraic data types, with compile-time exhaustiveness/redundancy check, - laziness (a value can be a memoized thunk), - automatic type class derivation - Generalised algebraic data types - combinators implementing lenses, prisms and optionals.

Algebraic data types come in two flavours, product types and sum types. This readme focus on sum types because it is the more interesting case; product types being the well known common case in Java, but Derive4J handles product types in exactly the same fashion (ie. through a visitor interface with a single abstract method).

Example: a 'Visitor' for HTTP Request

Let's say we want to model an HTTP request. For the sake of the example let's say that an HTTP request can either be - a GET on a given path - a DELETE on a given path - a POST of a content body on a given path - a PUT of a content body on a given path

and nothing else!

You could then use the corrected visitor pattern and write the following class in Java:

package org.derive4j.example;

/** A data type to model an http request. */
@Data
public abstract class Request {

  /** the Request 'visitor' interface, R being the return type
   *  used by the 'accept' method : */
  interface Cases<R> {
    // A request can either be a 'GET' (of a path):
    R GET(String path);
    // or a 'DELETE' (of a path):
    R DELETE(String path);
    // or a 'PUT' (on a path, with a body):
    R PUT(String path, String body);
    // or a 'POST' (on a path, with a body):
    R POST(String path, String body);
    // and nothing else!
  }

  // the 'accept' method of the visitor pattern:
  public abstract <R> R match(Cases<R> cases);

  /**
   * Alternatively and equivalently to the visitor pattern above, if you prefer a more FP style,
   * you can define a catamorphism instead. (see examples)
   * (most useful for standard data type like Option, Either, List...)
   */
}

Constructors

Without Derive4J, you would have to create subclasses of Request for all four cases. That is, write at the minimum something like:

  public static Request GET(String path) {
    return new Request() {
      @Override
      public <R> R match(Cases<R> cases) {
        return cases.GET(path);
      }
    };}

for each case. But thanks to the @Data annotation, Derive4j will do that for you! That is, it will generate a Requests class (the name is configurable, the class is generated by default in target/generated-sources/annotations when using Maven) with four static factory methods (what we call 'constructors' in FP):

  public static Request GET(String path) {...}
  public static Request DELETE(String path) {...}
  public static Request PUT(String path, String body) {...}
  public static Request POST(String path, String body) {...}

You can also ask Derive4J to generate null checks with:

@Data(arguments = ArgOption.checkedNotNull)

equals, hashCode, toString?

Derive4J philosophy is to be as safe and consistent as possible. That is why Object.{equals, hashCode, toString} are not implemented by generated classes by default (they are best kept ignored as they break parametricity). Nonetheless, as a concession to legacy, it is possible to force Derive4J to implement them, by declaring them abstract. Eg by adding the following in your annotated class:

  @Override
  public abstract int hashCode();
  @Override
  public abstract boolean equals(Object obj);
  @Override
  public abstract String toString();

The safer solution would be to never use those methods and use 'type classes' instead, eg. Equal, Hash and Show. The project Derive4J for Functional Java aims to generate them automatically.

Pattern matching syntaxes

Now let's say that you want a function that returns the body size of a Request. Without Derive4J you would write something like:

  static final Function<Request, Integer> getBodySize = request -> 
      request.match(new Cases<Integer>() {
        public Integer GET(String path) {
          return 0;
        }
        public Integer DELETE(String path) {
          return 0;
        }
        public Integer PUT(String path, String body) {
          return body.length();
        }
        public Integer POST(String path, String body) {
          return body.length();
        }
      });

With Derive4J you can do that a lot less verbosely, thanks to a generated fluent structural pattern matching syntaxes! And it does exhaustivity check! (you must handle all cases). The above can be rewritten into:

static final Function<Request, Integer> getBodySize = Requests.cases()
      .GET_(0) // shortcut for .Get(path -> 0)
      .DELETE_(0)
      .PUT((path, body)  -> body.length())
      .POST((path, body) -> body.length())

or even (because you don't care of GET and DELETE cases):

static final Function<Request, Integer> getBodySize = Requests.cases()
      .PUT((path, body)  -> body.length())
      .POST((path, body) -> body.length())
      .otherwise_(0)

Derive4j also allows to match directly against a value:

static int getBodyLength(Request request) {
  return Requests.caseOf(request)
      .PUT((path, body)  -> body.length())
      .POST((path, body) -> body.length())
      .otherwise_(0)
}

Accessors (getters)

Now, pattern matching every time you want to inspect an instance of Request is a bit tedious. For this reason Derive4J generates 'getter' static methods for all fields. For the path and body fields, Derive4J will generate the following methods in the Requests class:

  public static String getPath(Request request){
    return Requests.cases()
        .GET(path          -> path)
        .DELETE(path       -> path)
        .PUT((path, body)  -> path)
        .POST((path, body) -> path)
        .apply(request);
  }
  // return an Optional because the body is not present in the GET and DELETE cases:
  static Optional<String> getBody(Request request){
    return Requests.cases()
        .PUT((path, body)  -> body)
        .POST((path, body) -> body)
        .otherwiseEmpty()
        .apply(request);
  }

(Actually the generated code is equivalent but more efficient)

Using the generated getBody methods, we can rewrite our getBodySize function into:

static final Function<Request, Integer> getBodySize = request ->
      Requests.getBody(request)
              .map(String::length)
              .orElse(0);

Functional setters ('withers')

The most painful part of immutable data structures (like the one generated by Derive4J) is updating them. Scala case classes have copy methods for that. Derive4J generates similar modifier and setter methods in the Requests class:

  public static Function<Request, Request> setPath(String newPath){
    return Requests.cases()
            .GET(path          -> Requests.GET(newPath))
            .DELETE(path       -> Requests.DELETE(newPath))
            .PUT((path, body)  -> Requests.PUT(newPath, body))
            .POST((path, body) -> Requests.POST(newPath, body)));
  }
  public static Function<Request, Request> modPath(Function<String, String> pathMapper){
    return Requests.cases()
            .GET(path          -> Requests.GET(pathMapper.apply(path)))
            .DELETE(path       -> Requests.DELETE(pathMapper.apply(path)))
            .PUT((path, body)  -> Requests.PUT(pathMapper.apply(path), body))
            .POST((path, body) -> Requests.POST(pathMapper.apply(path), body)));
  }
  public static Function<Request, Request> setBody(String newBody){
    return Requests.cases()
            .GET(path          -> Requests.GET(path))    // identity function for GET
            .DELETE(path       -> Requests.DELETE(path)) // and DELETE cases.
            .PUT((path, body)  -> Requests.PUT(path, newBody))
            .POST((path, body) -> Requests.POST(path, newBody)));
  }
  ...

By returning a function, modifiers and setters allow for a lightweight syntax when updating deeply nested immutable data structures.

First class laziness

Languages like Haskell provide laziness by default, which simplifies a lot of algorithms. In traditional Java you would have to declare a method argument as Supplier<Request> (and do memoization) to emulate laziness. With Derive4J that is no more necessary as it generates a lazy constructor that gives you transparent lazy evaluation for all consumers of your data type:

  // the requestExpression will be lazy-evaluated on the first call
  // to the 'match' method of the returned Request instance:
  public static Request lazy(Supplier<Request> requestExpression) {
    ...
  }

Have a look at List for how to implement a lazy cons list in Java using Derive4J (you may also want to see the associated generated code).

Flavours

In the example above, we have used the default JDK flavour. Also available are FJ (Functional Java), Fugue (Fugue), Javaslang/Vavr (Vavr), HighJ (HighJ), Guava and Cyclops (Cyclops-react) flavours. When using those alternative flavours, Derive4J will use eg. the specific Option implementations from those projects instead of the jdk Optional class.

Optics (functional lenses)

If you are not familiar with optics, have a look at Monocle (for Scala, but Functional Java provides similar abstraction).

Using Derive4J generated code, defining optics is a breeze (you need to use the FJ flavour by specifying @Data(flavour = Flavour.FJ): ```java / * Lenses: optics focused on a field present for all data type constructors * (getter cannot 'failed'): */ public static final Lens _path = lens( Requests::getPath, Requests::setPath); / * Optional: optics focused on a field that may not be present for all constructors * (getter return an 'Option'): / public static final Optional _body = optional( Requests::getBody, Requests::setBody); / * Prism: optics focused on a specific constructor: / public static final Prism _GET = prism( // Getter fu

Extension points exported contracts — how you extend this code

Derivator (Interface)
(no doc) [18 implementers]
processor-api/src/main/java/org/derive4j/processor/api/Derivator.java
ArrayWrapper (Interface)
(no doc) [42 implementers]
examples/src/main/java/org/derive4j/example/ArrayWrapper.java
Cases (Interface)
(no doc)
annotation/src/main/java/org/derive4j/Flavour.java
DeriveUtils (Interface)
(no doc) [2 implementers]
processor-api/src/main/java/org/derive4j/processor/api/DeriveUtils.java
Stream (Interface)
(no doc)
examples/src/main/java/org/derive4j/example/Stream.java
VisibilityCases (Interface)
(no doc)
annotation/src/main/java/org/derive4j/Visibility.java
Extension (Interface)
(no doc) [2 implementers]
processor-api/src/main/java/org/derive4j/processor/api/Extension.java
Cases (Interface)
(no doc)
annotation/src/main/java/org/derive4j/Make.java

Core symbols most depended-on inside this repo

map
called by 288
processor-api/src/main/java/org/derive4j/processor/api/DeriveResult.java
get
called by 268
examples/src/main/java/org/derive4j/example/Stream.java
build
called by 178
examples/src/main/java/org/derive4j/example/Corecursive.java
typeConstructor
called by 99
processor-api/src/main/java/org/derive4j/processor/api/model/TypeConstructor.java
toString
called by 89
processor/src/main/java/org/derive4j/processor/P2.java
matchMethod
called by 80
processor-api/src/main/java/org/derive4j/processor/api/model/MatchMethod.java
concat
called by 67
processor-api/src/main/java/org/derive4j/processor/api/DerivedCodeSpec.java
filter
called by 64
examples/src/main/java/org/derive4j/example/List.java

Shape

Method 699
Class 87
Interface 61
Enum 10

Languages

Java100%

Modules by API surface

processor/src/main/java/org/derive4j/processor/DeriveUtilsImpl.java53 symbols
processor/src/main/java/org/derive4j/processor/DeriveConfigBuilder.java32 symbols
processor/src/main/java/org/derive4j/processor/Utils.java31 symbols
processor-api/src/main/java/org/derive4j/processor/api/DeriveUtils.java30 symbols
processor/src/main/java/org/derive4j/processor/StrictConstructorDerivator.java27 symbols
examples/src/main/java/org/derive4j/example/List.java23 symbols
examples/src/main/java/org/derive4j/example/Corecursive.java21 symbols
examples/src/main/java/org/derive4j/example/Request.java17 symbols
examples/src/main/java/org/derive4j/example/PhoneAndPers.java16 symbols
processor/src/test/java/org/derive4j/processor/CompileExamplesTest.java15 symbols
processor/src/main/java/org/derive4j/processor/GettersDerivator.java15 symbols
processor/src/main/java/org/derive4j/processor/OtherwiseMatchingStepDerivator.java14 symbols

Used by 1 indexed graphs manifest dependencies, hub-wide

For agents

$ claude mcp add derive4j \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact