0
# Tagged Unions
1
2
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.
3
4
## Basic Tagged Union
5
6
```java { .api }
7
@AutoOneOf(StringOrInteger.Kind.class)
8
public abstract class StringOrInteger {
9
public enum Kind {
10
STRING, INTEGER
11
}
12
13
public abstract Kind getKind();
14
15
public abstract String string();
16
public abstract int integer();
17
18
public static StringOrInteger ofString(String s) {
19
return AutoOneOf_StringOrInteger.string(s);
20
}
21
22
public static StringOrInteger ofInteger(int i) {
23
return AutoOneOf_StringOrInteger.integer(i);
24
}
25
}
26
```
27
28
## Usage Example
29
30
```java
31
StringOrInteger stringValue = StringOrInteger.ofString("hello");
32
StringOrInteger intValue = StringOrInteger.ofInteger(42);
33
34
// Pattern matching with switch
35
String result = processValue(stringValue);
36
37
public String processValue(StringOrInteger value) {
38
switch (value.getKind()) {
39
case STRING:
40
return "String: " + value.string();
41
case INTEGER:
42
return "Integer: " + value.integer();
43
}
44
throw new AssertionError("Unknown kind: " + value.getKind());
45
}
46
```
47
48
## Complex Tagged Union
49
50
```java { .api }
51
@AutoOneOf(Result.Kind.class)
52
public abstract class Result<T, E> {
53
public enum Kind {
54
SUCCESS, ERROR
55
}
56
57
public abstract Kind getKind();
58
59
public abstract T success();
60
public abstract E error();
61
62
public static <T, E> Result<T, E> success(T value) {
63
return AutoOneOf_Result.success(value);
64
}
65
66
public static <T, E> Result<T, E> error(E error) {
67
return AutoOneOf_Result.error(error);
68
}
69
}
70
```
71
72
Usage:
73
74
```java
75
Result<String, Exception> successResult = Result.success("Operation completed");
76
Result<String, Exception> errorResult = Result.error(new RuntimeException("Failed"));
77
78
// Processing results
79
public <T, E> void handleResult(Result<T, E> result) {
80
switch (result.getKind()) {
81
case SUCCESS:
82
System.out.println("Success: " + result.success());
83
break;
84
case ERROR:
85
System.err.println("Error: " + result.error().getMessage());
86
break;
87
}
88
}
89
```
90
91
## Multiple Type Union
92
93
```java { .api }
94
@AutoOneOf(Value.Kind.class)
95
public abstract class Value {
96
public enum Kind {
97
STRING, INTEGER, DOUBLE, BOOLEAN, LIST
98
}
99
100
public abstract Kind getKind();
101
102
public abstract String string();
103
public abstract int integer();
104
public abstract double doubleValue();
105
public abstract boolean booleanValue();
106
public abstract List<Value> list();
107
108
public static Value ofString(String s) {
109
return AutoOneOf_Value.string(s);
110
}
111
112
public static Value ofInteger(int i) {
113
return AutoOneOf_Value.integer(i);
114
}
115
116
public static Value ofDouble(double d) {
117
return AutoOneOf_Value.doubleValue(d);
118
}
119
120
public static Value ofBoolean(boolean b) {
121
return AutoOneOf_Value.booleanValue(b);
122
}
123
124
public static Value ofList(List<Value> list) {
125
return AutoOneOf_Value.list(list);
126
}
127
}
128
```
129
130
Usage:
131
132
```java
133
Value stringVal = Value.ofString("text");
134
Value numberVal = Value.ofInteger(123);
135
Value listVal = Value.ofList(Arrays.asList(stringVal, numberVal));
136
137
// JSON-like processing
138
public Object toJson(Value value) {
139
switch (value.getKind()) {
140
case STRING:
141
return value.string();
142
case INTEGER:
143
return value.integer();
144
case DOUBLE:
145
return value.doubleValue();
146
case BOOLEAN:
147
return value.booleanValue();
148
case LIST:
149
return value.list().stream()
150
.map(this::toJson)
151
.collect(Collectors.toList());
152
}
153
throw new AssertionError();
154
}
155
```
156
157
## Optional-like Union
158
159
```java { .api }
160
@AutoOneOf(Optional.Kind.class)
161
public abstract class Optional<T> {
162
public enum Kind {
163
PRESENT, ABSENT
164
}
165
166
public abstract Kind getKind();
167
168
public abstract T present();
169
public abstract Void absent(); // Void for empty case
170
171
public static <T> Optional<T> of(T value) {
172
return AutoOneOf_Optional.present(value);
173
}
174
175
public static <T> Optional<T> empty() {
176
return AutoOneOf_Optional.absent(null);
177
}
178
179
// Convenience methods
180
public boolean isPresent() {
181
return getKind() == Kind.PRESENT;
182
}
183
184
public boolean isEmpty() {
185
return getKind() == Kind.ABSENT;
186
}
187
188
public T orElse(T defaultValue) {
189
return isPresent() ? present() : defaultValue;
190
}
191
}
192
```
193
194
Usage:
195
196
```java
197
Optional<String> present = Optional.of("value");
198
Optional<String> empty = Optional.empty();
199
200
String result1 = present.orElse("default"); // "value"
201
String result2 = empty.orElse("default"); // "default"
202
```
203
204
## Recursive Tagged Unions
205
206
Tagged unions can reference themselves for tree-like structures:
207
208
```java { .api }
209
@AutoOneOf(Expression.Kind.class)
210
public abstract class Expression {
211
public enum Kind {
212
NUMBER, VARIABLE, BINARY_OP
213
}
214
215
public abstract Kind getKind();
216
217
public abstract double number();
218
public abstract String variable();
219
public abstract BinaryOp binaryOp();
220
221
public static Expression number(double value) {
222
return AutoOneOf_Expression.number(value);
223
}
224
225
public static Expression variable(String name) {
226
return AutoOneOf_Expression.variable(name);
227
}
228
229
public static Expression binaryOp(String operator, Expression left, Expression right) {
230
return AutoOneOf_Expression.binaryOp(BinaryOp.create(operator, left, right));
231
}
232
233
@AutoValue
234
public abstract static class BinaryOp {
235
public abstract String operator();
236
public abstract Expression left();
237
public abstract Expression right();
238
239
public static BinaryOp create(String operator, Expression left, Expression right) {
240
return new AutoValue_Expression_BinaryOp(operator, left, right);
241
}
242
}
243
}
244
```
245
246
Usage:
247
248
```java
249
// Build expression: (x + 2) * 3
250
Expression x = Expression.variable("x");
251
Expression two = Expression.number(2);
252
Expression three = Expression.number(3);
253
Expression xPlusTwo = Expression.binaryOp("+", x, two);
254
Expression result = Expression.binaryOp("*", xPlusTwo, three);
255
256
// Evaluate expression
257
public double evaluate(Expression expr, Map<String, Double> variables) {
258
switch (expr.getKind()) {
259
case NUMBER:
260
return expr.number();
261
case VARIABLE:
262
return variables.get(expr.variable());
263
case BINARY_OP:
264
BinaryOp op = expr.binaryOp();
265
double left = evaluate(op.left(), variables);
266
double right = evaluate(op.right(), variables);
267
switch (op.operator()) {
268
case "+": return left + right;
269
case "*": return left * right;
270
// ... other operators
271
}
272
throw new IllegalArgumentException("Unknown operator: " + op.operator());
273
}
274
throw new AssertionError();
275
}
276
```
277
278
## Nullable Variants
279
280
Tagged unions can have nullable variants:
281
282
```java { .api }
283
@AutoOneOf(NullableValue.Kind.class)
284
public abstract class NullableValue<T> {
285
public enum Kind {
286
VALUE, NULL
287
}
288
289
public abstract Kind getKind();
290
291
@Nullable
292
public abstract T value();
293
public abstract Void nullValue();
294
295
public static <T> NullableValue<T> of(@Nullable T value) {
296
return value != null
297
? AutoOneOf_NullableValue.value(value)
298
: AutoOneOf_NullableValue.nullValue(null);
299
}
300
301
public static <T> NullableValue<T> nullValue() {
302
return AutoOneOf_NullableValue.nullValue(null);
303
}
304
}
305
```
306
307
## Error Handling in Tagged Unions
308
309
Use tagged unions for comprehensive error handling:
310
311
```java { .api }
312
@AutoOneOf(ApiResponse.Kind.class)
313
public abstract class ApiResponse<T> {
314
public enum Kind {
315
SUCCESS, CLIENT_ERROR, SERVER_ERROR, NETWORK_ERROR
316
}
317
318
public abstract Kind getKind();
319
320
public abstract T success();
321
public abstract ClientError clientError();
322
public abstract ServerError serverError();
323
public abstract NetworkError networkError();
324
325
public static <T> ApiResponse<T> success(T data) {
326
return AutoOneOf_ApiResponse.success(data);
327
}
328
329
public static <T> ApiResponse<T> clientError(int code, String message) {
330
return AutoOneOf_ApiResponse.clientError(ClientError.create(code, message));
331
}
332
333
public static <T> ApiResponse<T> serverError(int code, String message) {
334
return AutoOneOf_ApiResponse.serverError(ServerError.create(code, message));
335
}
336
337
public static <T> ApiResponse<T> networkError(Exception cause) {
338
return AutoOneOf_ApiResponse.networkError(NetworkError.create(cause));
339
}
340
341
@AutoValue
342
public abstract static class ClientError {
343
public abstract int code();
344
public abstract String message();
345
346
static ClientError create(int code, String message) {
347
return new AutoValue_ApiResponse_ClientError(code, message);
348
}
349
}
350
351
@AutoValue
352
public abstract static class ServerError {
353
public abstract int code();
354
public abstract String message();
355
356
static ServerError create(int code, String message) {
357
return new AutoValue_ApiResponse_ServerError(code, message);
358
}
359
}
360
361
@AutoValue
362
public abstract static class NetworkError {
363
public abstract Exception cause();
364
365
static NetworkError create(Exception cause) {
366
return new AutoValue_ApiResponse_NetworkError(cause);
367
}
368
}
369
}
370
```
371
372
Usage:
373
374
```java
375
ApiResponse<User> response = apiCall();
376
377
switch (response.getKind()) {
378
case SUCCESS:
379
User user = response.success();
380
displayUser(user);
381
break;
382
case CLIENT_ERROR:
383
ClientError error = response.clientError();
384
showError("Client error " + error.code() + ": " + error.message());
385
break;
386
case SERVER_ERROR:
387
ServerError error = response.serverError();
388
showError("Server error " + error.code() + ": " + error.message());
389
break;
390
case NETWORK_ERROR:
391
NetworkError error = response.networkError();
392
showError("Network error: " + error.cause().getMessage());
393
retry();
394
break;
395
}
396
```
397
398
## Builder Integration
399
400
Tagged unions work with builders for complex construction:
401
402
```java { .api }
403
@AutoOneOf(Message.Kind.class)
404
public abstract class Message {
405
public enum Kind {
406
TEXT, IMAGE, FILE
407
}
408
409
public abstract Kind getKind();
410
411
public abstract TextMessage text();
412
public abstract ImageMessage image();
413
public abstract FileMessage file();
414
415
@AutoValue
416
public abstract static class TextMessage {
417
public abstract String content();
418
public abstract Optional<String> format();
419
420
public static Builder builder() {
421
return new AutoValue_Message_TextMessage.Builder();
422
}
423
424
@AutoValue.Builder
425
public abstract static class Builder {
426
public abstract Builder content(String content);
427
public abstract Builder format(String format);
428
public abstract TextMessage build();
429
}
430
}
431
432
// Similar for ImageMessage and FileMessage...
433
434
public static Message text(String content) {
435
return AutoOneOf_Message.text(
436
TextMessage.builder().content(content).build());
437
}
438
}
439
```
440
441
## Performance Considerations
442
443
- Generated classes use efficient switch-based dispatching
444
- No boxing/unboxing for primitive types
445
- Generated equals() and hashCode() are optimized
446
- Kind enum provides O(1) type checking
447
- No reflection used at runtime