Skip to content

Instantly share code, notes, and snippets.

@david-bakin
Created November 7, 2015 03:40
Show Gist options
  • Save david-bakin/d880167c3f3c35f22455 to your computer and use it in GitHub Desktop.
Save david-bakin/d880167c3f3c35f22455 to your computer and use it in GitHub Desktop.
Either type for Java
package com.bakins_bits.types;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import com.google.common.base.Preconditions;
/**
* Java doesn't have sum types. So here's an implementation of Either. Immutable type (that is, this class is immutable,
* but the value of the value is not). Also, it refuses to hold nulls, so if you desperately want a null value, wrap the
* type in Optional.
* <p>
* TODO: Consider using derive4j (https://github.com/derive4j/derive4j) or something similar to generate these things.
*/
public abstract class Either<L, R>
{
private Either() {
}
public static <L, R> Either<L, R> newLeft(L left) {
return new LEither<L, R>(left);
}
public static <L, R> Either<L, R> newRight(R right) {
return new REither<L, R>(right);
}
public abstract void fold(Consumer<L> cl, Consumer<R> cr);
public abstract <RES> RES fold(Function<L, RES> cl, Function<R, RES> cr);
protected abstract Object value();
@Override
public boolean equals(Object obj) {
if (null == obj)
return false;
if (this == obj)
return true;
if (this.getClass() != obj.getClass())
return false;
@SuppressWarnings("rawtypes")
Either rhs = (Either) obj;
return new EqualsBuilder().append(this.value(), rhs.value()).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(1, 1).append(value()).toHashCode();
}
private static class LEither<L, R> extends Either<L, R>
{
private final L left;
private LEither(L left) {
Preconditions.checkNotNull(left, "left must not be null");
this.left = left;
}
protected L value() {
return left;
}
@Override
public void fold(Consumer<L> cl, Consumer<R> cr) {
Preconditions.checkNotNull(cl, "cl must not be null");
Preconditions.checkNotNull(cr, "cr must not be null");
cl.accept(left);
}
@Override
public <RES> RES fold(Function<L, RES> cl, Function<R, RES> cr) {
Preconditions.checkNotNull(cl, "cl must not be null");
Preconditions.checkNotNull(cr, "cr must not be null");
return cl.apply(left);
}
@Override
public String toString() {
return "L:" + left.toString();
}
}
private static class REither<L, R> extends Either<L, R>
{
private final R right;
private REither(R right) {
Preconditions.checkNotNull(right, "right must not be null");
this.right = right;
}
protected R value() {
return right;
}
@Override
public void fold(Consumer<L> cl, Consumer<R> cr) {
Preconditions.checkNotNull(cl, "cl must not be null");
Preconditions.checkNotNull(cr, "cr must not be null");
cr.accept(right);
}
@Override
public <RES> RES fold(Function<L, RES> cl, Function<R, RES> cr) {
Preconditions.checkNotNull(cl, "cl must not be null");
Preconditions.checkNotNull(cr, "cr must not be null");
return cr.apply(right);
}
@Override
public String toString() {
return "R:" + right.toString();
}
}
}
package com.bakins_bits.types;
import static org.assertj.core.api.Assertions.*;
import org.testng.annotations.Test;
/**
* Test of Either<L,R> class.
*/
public class TestEither
{
@Test(groups = { "unit" })
public void fold_consumer_works() {
// ARRANGE
Either<Integer, String> sut1 = Either.newLeft(25);
Either<Integer, String> sut2 = Either.newRight("hello there");
// ACT & ASSERT
boolean[] executed = { false };
sut1.fold(i -> {
executed[0] = true;
} , s -> {
fail("right function executed on left instance");
});
assertThat(executed[0]).isTrue();
executed[0] = false;
sut2.fold(i -> {
fail("left function executed on right instance");
} , s -> {
executed[0] = true;
});
assertThat(executed[0]).isTrue();
}
@Test(groups = { "unit" })
public void fold_function_works() {
// ARRANGE
Either<Integer, String> sut1 = Either.newLeft(25);
Either<Integer, String> sut2 = Either.newRight("hello there");
// ACT & ASSERT
assertThat(sut1.fold(i -> 2 * i, s -> {
fail("right function executed on left instance");
return "boo!";
})).isEqualTo(50);
assertThat(sut2.fold(i -> {
fail("left function executed on left instance");
return -1;
} , s -> s.toUpperCase())).isEqualTo("HELLO THERE");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment