CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-google-auto-value--auto-value

Generated immutable value classes for Java 8+ using annotation processing

Pending
Overview
Eval results
Files

tagged-unions.mddocs/

Tagged Unions

AutoOneOf generates tagged union (also known as sum types or algebraic data types) implementations that can represent a value that is exactly one of several possible types.

Basic Tagged Union

@AutoOneOf(StringOrInteger.Kind.class)
public abstract class StringOrInteger {
  public enum Kind {
    STRING, INTEGER
  }
  
  public abstract Kind getKind();
  
  public abstract String string();
  public abstract int integer();
  
  public static StringOrInteger ofString(String s) {
    return AutoOneOf_StringOrInteger.string(s);
  }
  
  public static StringOrInteger ofInteger(int i) {
    return AutoOneOf_StringOrInteger.integer(i);
  }
}

Usage Example

StringOrInteger stringValue = StringOrInteger.ofString("hello");
StringOrInteger intValue = StringOrInteger.ofInteger(42);

// Pattern matching with switch
String result = processValue(stringValue);

public String processValue(StringOrInteger value) {
  switch (value.getKind()) {
    case STRING:
      return "String: " + value.string();
    case INTEGER:
      return "Integer: " + value.integer();
  }
  throw new AssertionError("Unknown kind: " + value.getKind());
}

Complex Tagged Union

@AutoOneOf(Result.Kind.class)
public abstract class Result<T, E> {
  public enum Kind {
    SUCCESS, ERROR
  }
  
  public abstract Kind getKind();
  
  public abstract T success();
  public abstract E error();
  
  public static <T, E> Result<T, E> success(T value) {
    return AutoOneOf_Result.success(value);
  }
  
  public static <T, E> Result<T, E> error(E error) {
    return AutoOneOf_Result.error(error);
  }
}

Usage:

Result<String, Exception> successResult = Result.success("Operation completed");
Result<String, Exception> errorResult = Result.error(new RuntimeException("Failed"));

// Processing results
public <T, E> void handleResult(Result<T, E> result) {
  switch (result.getKind()) {
    case SUCCESS:
      System.out.println("Success: " + result.success());
      break;
    case ERROR:
      System.err.println("Error: " + result.error().getMessage());
      break;
  }
}

Multiple Type Union

@AutoOneOf(Value.Kind.class)
public abstract class Value {
  public enum Kind {
    STRING, INTEGER, DOUBLE, BOOLEAN, LIST
  }
  
  public abstract Kind getKind();
  
  public abstract String string();
  public abstract int integer();
  public abstract double doubleValue();
  public abstract boolean booleanValue();
  public abstract List<Value> list();
  
  public static Value ofString(String s) {
    return AutoOneOf_Value.string(s);
  }
  
  public static Value ofInteger(int i) {
    return AutoOneOf_Value.integer(i);
  }
  
  public static Value ofDouble(double d) {
    return AutoOneOf_Value.doubleValue(d);
  }
  
  public static Value ofBoolean(boolean b) {
    return AutoOneOf_Value.booleanValue(b);
  }
  
  public static Value ofList(List<Value> list) {
    return AutoOneOf_Value.list(list);
  }
}

Usage:

Value stringVal = Value.ofString("text");
Value numberVal = Value.ofInteger(123);
Value listVal = Value.ofList(Arrays.asList(stringVal, numberVal));

// JSON-like processing
public Object toJson(Value value) {
  switch (value.getKind()) {
    case STRING:
      return value.string();
    case INTEGER:
      return value.integer();
    case DOUBLE:
      return value.doubleValue();
    case BOOLEAN:
      return value.booleanValue();
    case LIST:
      return value.list().stream()
          .map(this::toJson)
          .collect(Collectors.toList());
  }
  throw new AssertionError();
}

Optional-like Union

@AutoOneOf(Optional.Kind.class)
public abstract class Optional<T> {
  public enum Kind {
    PRESENT, ABSENT
  }
  
  public abstract Kind getKind();
  
  public abstract T present();
  public abstract Void absent(); // Void for empty case
  
  public static <T> Optional<T> of(T value) {
    return AutoOneOf_Optional.present(value);
  }
  
  public static <T> Optional<T> empty() {
    return AutoOneOf_Optional.absent(null);
  }
  
  // Convenience methods
  public boolean isPresent() {
    return getKind() == Kind.PRESENT;
  }
  
  public boolean isEmpty() {
    return getKind() == Kind.ABSENT;
  }
  
  public T orElse(T defaultValue) {
    return isPresent() ? present() : defaultValue;
  }
}

Usage:

Optional<String> present = Optional.of("value");
Optional<String> empty = Optional.empty();

String result1 = present.orElse("default"); // "value"
String result2 = empty.orElse("default");   // "default"

Recursive Tagged Unions

Tagged unions can reference themselves for tree-like structures:

@AutoOneOf(Expression.Kind.class)
public abstract class Expression {
  public enum Kind {
    NUMBER, VARIABLE, BINARY_OP
  }
  
  public abstract Kind getKind();
  
  public abstract double number();
  public abstract String variable();
  public abstract BinaryOp binaryOp();
  
  public static Expression number(double value) {
    return AutoOneOf_Expression.number(value);
  }
  
  public static Expression variable(String name) {
    return AutoOneOf_Expression.variable(name);
  }
  
  public static Expression binaryOp(String operator, Expression left, Expression right) {
    return AutoOneOf_Expression.binaryOp(BinaryOp.create(operator, left, right));
  }
  
  @AutoValue
  public abstract static class BinaryOp {
    public abstract String operator();
    public abstract Expression left();
    public abstract Expression right();
    
    public static BinaryOp create(String operator, Expression left, Expression right) {
      return new AutoValue_Expression_BinaryOp(operator, left, right);
    }
  }
}

Usage:

// Build expression: (x + 2) * 3
Expression x = Expression.variable("x");
Expression two = Expression.number(2);
Expression three = Expression.number(3);
Expression xPlusTwo = Expression.binaryOp("+", x, two);
Expression result = Expression.binaryOp("*", xPlusTwo, three);

// Evaluate expression
public double evaluate(Expression expr, Map<String, Double> variables) {
  switch (expr.getKind()) {
    case NUMBER:
      return expr.number();
    case VARIABLE:
      return variables.get(expr.variable());
    case BINARY_OP:
      BinaryOp op = expr.binaryOp();
      double left = evaluate(op.left(), variables);
      double right = evaluate(op.right(), variables);
      switch (op.operator()) {
        case "+": return left + right;
        case "*": return left * right;
        // ... other operators
      }
      throw new IllegalArgumentException("Unknown operator: " + op.operator());
  }
  throw new AssertionError();
}

Nullable Variants

Tagged unions can have nullable variants:

@AutoOneOf(NullableValue.Kind.class)
public abstract class NullableValue<T> {
  public enum Kind {
    VALUE, NULL
  }
  
  public abstract Kind getKind();
  
  @Nullable
  public abstract T value();
  public abstract Void nullValue();
  
  public static <T> NullableValue<T> of(@Nullable T value) {
    return value != null 
        ? AutoOneOf_NullableValue.value(value)
        : AutoOneOf_NullableValue.nullValue(null);
  }
  
  public static <T> NullableValue<T> nullValue() {
    return AutoOneOf_NullableValue.nullValue(null);
  }
}

Error Handling in Tagged Unions

Use tagged unions for comprehensive error handling:

@AutoOneOf(ApiResponse.Kind.class)
public abstract class ApiResponse<T> {
  public enum Kind {
    SUCCESS, CLIENT_ERROR, SERVER_ERROR, NETWORK_ERROR
  }
  
  public abstract Kind getKind();
  
  public abstract T success();
  public abstract ClientError clientError();
  public abstract ServerError serverError(); 
  public abstract NetworkError networkError();
  
  public static <T> ApiResponse<T> success(T data) {
    return AutoOneOf_ApiResponse.success(data);
  }
  
  public static <T> ApiResponse<T> clientError(int code, String message) {
    return AutoOneOf_ApiResponse.clientError(ClientError.create(code, message));
  }
  
  public static <T> ApiResponse<T> serverError(int code, String message) {
    return AutoOneOf_ApiResponse.serverError(ServerError.create(code, message));
  }
  
  public static <T> ApiResponse<T> networkError(Exception cause) {
    return AutoOneOf_ApiResponse.networkError(NetworkError.create(cause));
  }
  
  @AutoValue
  public abstract static class ClientError {
    public abstract int code();
    public abstract String message();
    
    static ClientError create(int code, String message) {
      return new AutoValue_ApiResponse_ClientError(code, message);
    }
  }
  
  @AutoValue
  public abstract static class ServerError {
    public abstract int code();
    public abstract String message();
    
    static ServerError create(int code, String message) {
      return new AutoValue_ApiResponse_ServerError(code, message);
    }
  }
  
  @AutoValue
  public abstract static class NetworkError {
    public abstract Exception cause();
    
    static NetworkError create(Exception cause) {
      return new AutoValue_ApiResponse_NetworkError(cause);
    }
  }
}

Usage:

ApiResponse<User> response = apiCall();

switch (response.getKind()) {
  case SUCCESS:
    User user = response.success();
    displayUser(user);
    break;
  case CLIENT_ERROR:
    ClientError error = response.clientError();
    showError("Client error " + error.code() + ": " + error.message());
    break;
  case SERVER_ERROR:
    ServerError error = response.serverError();
    showError("Server error " + error.code() + ": " + error.message());
    break;
  case NETWORK_ERROR:
    NetworkError error = response.networkError();
    showError("Network error: " + error.cause().getMessage());
    retry();
    break;
}

Builder Integration

Tagged unions work with builders for complex construction:

@AutoOneOf(Message.Kind.class)
public abstract class Message {
  public enum Kind {
    TEXT, IMAGE, FILE
  }
  
  public abstract Kind getKind();
  
  public abstract TextMessage text();
  public abstract ImageMessage image();
  public abstract FileMessage file();
  
  @AutoValue
  public abstract static class TextMessage {
    public abstract String content();
    public abstract Optional<String> format();
    
    public static Builder builder() {
      return new AutoValue_Message_TextMessage.Builder();
    }
    
    @AutoValue.Builder
    public abstract static class Builder {
      public abstract Builder content(String content);
      public abstract Builder format(String format);
      public abstract TextMessage build();
    }
  }
  
  // Similar for ImageMessage and FileMessage...
  
  public static Message text(String content) {
    return AutoOneOf_Message.text(
        TextMessage.builder().content(content).build());
  }
}

Performance Considerations

  • Generated classes use efficient switch-based dispatching
  • No boxing/unboxing for primitive types
  • Generated equals() and hashCode() are optimized
  • Kind enum provides O(1) type checking
  • No reflection used at runtime

Install with Tessl CLI

npx tessl i tessl/maven-com-google-auto-value--auto-value

docs

annotation-generation.md

builders.md

extensions.md

index.md

memoization.md

pretty-strings.md

serialization.md

standalone-builders.md

tagged-unions.md

value-classes.md

tile.json