Created
November 7, 2015 03:40
-
-
Save david-bakin/d880167c3f3c35f22455 to your computer and use it in GitHub Desktop.
Either type for Java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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