Skip to content

Instantly share code, notes, and snippets.

@patrickhammond
Last active August 29, 2015 14:26
Show Gist options
  • Save patrickhammond/bfad10a75a9b21e1d36c to your computer and use it in GitHub Desktop.
Save patrickhammond/bfad10a75a9b21e1d36c to your computer and use it in GitHub Desktop.
Sample of how we might make variant types suck less with Java. See: https://en.wikipedia.org/wiki/Tagged_union for part of the CS theory.
// ------------------------------------------------------------------------
// App code: These classes that describe the state of executing a query
// that we want to consume as a stream of events.
public class Loading {
}
public class Results<T> {
final List<T> results;
public Results(List<T> results) {
this.results = results;
}
}
public class Error {
final String reason;
public Error(String reason) {
this.reason = reason;
}
}
// ------------------------------------------------------------------------
// App code: How we would trigger the code generator
@Variant
public interface Query {
Loading loading();
Results<String> results();
Error error();
}
// ------------------------------------------------------------------------
// API code: The annotation being used...no magic. Just here for reference.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Variant {
}
// ------------------------------------------------------------------------
// Generated code: nothing special here...just code no one wants to write
public final class VariantQuery implements Query {
private final Loading loading;
private final Results<String> results;
private final Error error;
public VariantQuery(Loading loading) {
this.loading = loading;
this.results = null;
this.error = null;
}
public VariantQuery(Results<String> results) {
this.loading = null;
this.results = results;
this.error = null;
}
public VariantQuery(Error error) {
this.loading = null;
this.results = null;
this.error = error;
}
@Override
public Loading loading() { return loading; } // Typically not used?
@Override
public Results<String> results() { return results; } // Typically not used?
@Override
public Error error() { return error; } // Typically not used?
void accept(VariantQueryVisitor visitor) {
if (loading != null) {
visitor.visitLoading(loading);
return;
}
if (results != null) {
visitor.visitResults(results);
return;
}
if (error != null) {
visitor.visitError(error);
return;
}
}
public interface VariantQueryVisitor {
void visitLoading(Loading loading);
void visitResults(Results<String> results);
void visitError(Error error);
}
}
// ------------------------------------------------------------------------
// App code: How we would use this in app code
Observable<VariantQuery> observable = Observable.just(
new VariantQuery(new Loading()),
new VariantQuery(new Results<>(Arrays.asList("A", "B"))),
new VariantQuery(new Results<>(Arrays.asList("C", "D"))));
observable.subscribe(new Action1<VariantQuery>() {
@Override
public void call(VariantQuery variantQuery) {
// Visitor implementation *should* be a variable vs
// constantly instantiated like this, but for now this
// is easier to follow
variantQuery.accept(new VariantQueryVisitor() {
@Override
public void visitLoading(Loading loading) {
// Show a progress indicator
}
@Override
public void visitResults(Results<String> results) {
// Display (or add to existing) results
}
@Override
public void visitError(Error error) {
// Show an error
}
});
}
});
  • Java doesn't have case classes like Scala
  • Writing code for variant type wrappers and visitors is annoying
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment