Skip to content

Latest commit

 

History

History
222 lines (168 loc) · 6.01 KB

README.adoc

File metadata and controls

222 lines (168 loc) · 6.01 KB

Java ADT Generator

Algebraic Data-Types for Java.

datatyper-maven-plugin is an Apache Maven plugin to generate immutable, algebraic data types for use in JDK 8 based projects.

Example DataTyper File

Request.typer
-- A simple model for an HTTP based API
package com.theoryinpractise.typer.examples;

-- By not specifying the return type with "->" for a given case,
-- the inferred return type should just be the individual case.
data Request implements (com.theoryinpractise.typer.Shouter)
  = GET    (path : String)
  | DELETE (path : String)
  | POST   (path : String, body : String);

-- By not specifying any arguments for a data type, it is generated as a
-- singleton instance which could be used in place of standard enums.
data Day
  = Monday | Tuesday | Wednesday
  | Thursday | Friday | Saturday | Sunday;

-- java.lang is "imported" by default, but if you want to use other classes,
-- simply import them

import java.util.Date;

data Log
  = info(date: Date, message: String)
  | debug(date: Date, message: String)
  | warn(date: Date, message: String)
  | error(date: Date, message: String);

Generated Classes

  • A top level abstract class acting as a module for the datatype.

  • Separate inner classes and constructor functions for each alterative type.

  • A matcher interface providing total coverage for each alternative type.

  • A fluent matching class for simple, inline matching.

A top-level abstract class is generated for each datatype, with additional abstract subclasses for each individual case, these classes are annotatd with @AutoValue and will generate concrete, immutable, private instance classes via the Google Autovalue library.

Static methods on the top level class are provided to construct instances of your dataypes.

The constructor methods return their concrete type, allowing local code to easily access the types members.

Request.GET("/api/story/32").path()

Matching values

Total Match Coverage via Visitor

An inner Matcher interface is also generated, containing a separate match method for each unique datatype case:

public interface Matcher<Return> {
  Return GET(Request.GET GET);
  Return DELETE(Request.DELETE DELETE);
  Return POST(Request.POST POST);
  ...
}

which is used in conjunction with the instance level match method defined in the top level abstract class, this is used as such:

int pathLength = req.match(new Request.Matcher() {
  @Override
  public Integer GET(Request.GET GET) {
    return GET.path().length();
  }

  @Override
  public Integer DELETE(Request.DELETE DELETE) {
    return DELETE.path().length();
  }

  @Override
  public Integer POST(Request.POST POST) {
    return POST.path().length();
  }
});

Fluent Matchers

DataTyper supports an alternate matching style based on a fluent lambda syntax:

Request.Matching<Integer> matched = req.matching()
  .GET( get -> get.path().length() );

if (matched.isMatched()) {
  int length = matched.get();
}

int length = req.<Integer>matching()
  .GET( get -> get.path().length() )
  .orElse(0);

Optional<Integer> length = req.<Integer>matching()
  .GET( get -> get.path().length() )
  .find();

The generated Request.Matching class is somewhat akin to an Optional value, the only difference being individual type case methods providing a functional style match expression.

Accepting Values

There are times you simply want to consume a specific value and don’t require a result, for this we have a Request.Accepting fluent interface:

public interface Accepting<Return> {
  void GET(Request.GET GET);
  void DELETE(Request.DELETE DELETE);
  void POST(Request.POST POST);
  ...
}

and is used:

Request.Matching<Integer> matched = req.accepting()
  .DELETE( delete -> handleDeleting(delete.path()) )
  .orElse(req -> throw UnsupportedOperation("lol wut?"));

Supporting Super-Type marker interfaces

data SomeType implements (some.marker.Interface, some.other.Interface)
  = Value();

Data Type declarations define a list of Java class names that the base class should implement. These classes MUST be interfaces, and only contain static or default methods ( otherwise the generated code will be fail to compile ).

Note

I really don’t like the ()) style syntax, but as yet I’m not sure yet how to get the jparsec parser library to terminate the CSV list without failing the parse. This is being tracked as issue #8.

Compile time dependencies

The code generated by datatyper-maven-plugin uses the Google Auto-Value annotations to generate it’s immutable classes, so this is required to be listed as a compile dependency in your maven project.

Note
There are no run-time dependencies introduced by the DataTyper project.

Configuring Your Maven Build

Standard Usage

pom.xml
<plugins>
  <plugin>
    <groupId>com.theoryinpractise.datatyper</groupId>
    <artifactId>datatyper-maven-plugin</artifactId>
    <version>1.0.1</version>
    <executions>
      <execution>
        <id>datatyper</id>
        <goals>
          <goal>datatyper</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>
...
<dependencies>
  <dependency>
    <groupId>com.google.auto.value</groupId>
    <artifactId>auto-value</artifactId>
    <version>1.3</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

Maven Tiles Usage

<plugins>
  <plugin>
    <groupId>io.repaint.maven</groupId>
    <artifactId>tiles-maven-plugin</artifactId>
    <version>2.10</version>
    <extensions>true</extensions>
    <configuration>
      <tiles>
        <tile>com.theoryinpractise.datatyper:datatyper-maven-tile:[1.0.0,2.0.0)</tile>
      </tiles>
    </configuration>
  </plugin>
</plugins>