Add flatMapLeft/Right and getLeft/Right to Either (#128)

* clean up code

* tree: replace deprecated assertj methods

* either: add flatMapLeft and flatMapRight

* either: add getLeft and getRight
This commit is contained in:
Paul Campbell 2020-06-20 15:03:52 +01:00 committed by GitHub
parent d617ffdf3f
commit 30f903dbb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 503 additions and 98 deletions

View file

@ -1184,3 +1184,44 @@
#+END_SRC #+END_SRC
*** =<T> Either<T, R> flatMapLeft(Function<L, Either<T, R>> f)=
FlatMap the function across the left value.
#+being_src java
Either<Integer, String> either = Either.left(2);
Either<Integer, String> resultLeft = either.flatMapLeft(l -> Either.left(l * 2));
Either<Integer, String> resultRight = either.flatMapLeft(l -> Either.right(l * 2));
#+end_src
*** =<T> Either<T, R> flatMapRight(Function<L, Either<T, R>> f)=
FlatMap the function across the right value.
#+being_src java
Either<Integer, String> either = Either.right("2");
Either<Integer, String> resultLeft = either.flatMapRight(l -> Either.left(l * 2));
Either<Integer, String> resultRight = either.flatMapRight(l -> Either.right(l * 2));
#+end_src
*** =Optional<L> getLeft()=
Returns an Optional containing the left value, if is a left, otherwise returns an empty Optional.
#+being_src java
Either<Integer, String> either = Either.right("2");
Optional<Integer> left = either.getLeft();
#+end_src
*** =Optional<R> getRight()=
Returns an Optional containing the right value, if is a right, otherwise returns an empty Optional.
#+being_src java
Either<Integer, String> either = Either.right("2");
Optional<String> right = either.getRight();
#+end_src

View file

@ -21,6 +21,7 @@
package net.kemitix.mon.experimental.either; package net.kemitix.mon.experimental.either;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -31,6 +32,7 @@ import java.util.function.Function;
* @param <R> the type of the Either for right value * @param <R> the type of the Either for right value
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */
@SuppressWarnings("methodcount")
public interface Either<L, R> { public interface Either<L, R> {
/** /**
@ -96,4 +98,38 @@ public interface Either<L, R> {
* @return a new Either * @return a new Either
*/ */
public abstract <T> Either<L, T> mapRight(Function<R, T> f); public abstract <T> Either<L, T> mapRight(Function<R, T> f);
/**
* FlatMap the function across the left value.
*
* @param f the function to apply to any left value
* @param <T> the type to change the left value to
* @return a new Either if is a Left, else this
*/
public abstract <T> Either<T, R> flatMapLeft(Function<L, Either<T, R>> f);
/**
* FlatMap the function across the right value.
*
* @param f the function to apply to any right value
* @param <T> the type to change the right value to
* @return a new Either if is a Right, else this
*/
public abstract <T> Either<L, T> flatMapRight(Function<R, Either<L, T>> f);
/**
* Returns an Optional containing the left value, if is a left, otherwise
* returns an empty Optional.
*
* @return An Optional containing any left value
*/
public abstract Optional<L> getLeft();
/**
* Returns an Optional containing the right value, if is a right, otherwise
* returns an empty Optional.
*
* @return An Optional containing any right value
*/
public abstract Optional<R> getRight();
} }

View file

@ -23,6 +23,7 @@ package net.kemitix.mon.experimental.either;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -62,4 +63,25 @@ class Left<L, R> implements Either<L, R> {
public <T> Either<L, T> mapRight(final Function<R, T> f) { public <T> Either<L, T> mapRight(final Function<R, T> f) {
return new Left<>(value); return new Left<>(value);
} }
@Override
public <T> Either<T, R> flatMapLeft(final Function<L, Either<T, R>> f) {
return f.apply(value);
}
@Override
@SuppressWarnings("unchecked")
public <T> Either<L, T> flatMapRight(final Function<R, Either<L, T>> f) {
return (Either<L, T>) this;
}
@Override
public Optional<L> getLeft() {
return Optional.ofNullable(value);
}
@Override
public Optional<R> getRight() {
return Optional.empty();
}
} }

View file

@ -23,6 +23,7 @@ package net.kemitix.mon.experimental.either;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -62,4 +63,26 @@ class Right<L, R> implements Either<L, R> {
public <T> Either<L, T> mapRight(final Function<R, T> f) { public <T> Either<L, T> mapRight(final Function<R, T> f) {
return new Right<>(f.apply(value)); return new Right<>(f.apply(value));
} }
@Override
@SuppressWarnings("unchecked")
public <T> Either<T, R> flatMapLeft(final Function<L, Either<T, R>> f) {
return (Either<T, R>) this;
}
@Override
public <T> Either<L, T> flatMapRight(final Function<R, Either<L, T>> f) {
return f.apply(value);
}
@Override
public Optional<L> getLeft() {
return Optional.empty();
}
@Override
public Optional<R> getRight() {
return Optional.ofNullable(value);
}
} }

View file

@ -2,113 +2,396 @@ package net.kemitix.mon;
import net.kemitix.mon.experimental.either.Either; import net.kemitix.mon.experimental.either.Either;
import org.assertj.core.api.WithAssertions; import org.assertj.core.api.WithAssertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.Optional;
import java.util.function.BiFunction;
class EitherTest implements WithAssertions { class EitherTest implements WithAssertions {
@Nested
@Test @DisplayName("isLeft()")
void whenLeft_isLeft() { public class IsLeft {
//when @Test
final Either<Integer, String> either = Either.left(1); void whenLeft_isLeft() {
//then //when
assertThat(either.isLeft()).isTrue(); final Either<Integer, String> either = Either.left(1);
//then
assertThat(either.isLeft()).isTrue();
}
@Test
void whenRight_isNotLeft() {
//when
final Either<Integer, String> either = Either.right("1");
//then
assertThat(either.isLeft()).isFalse();
}
} }
@Nested
@Test @DisplayName("isRight()")
void whenLeft_isNotRight() { public class IsRight {
//when @Test
final Either<Integer, String> either = Either.left(1); void whenLeft_isNotRight() {
//then //when
assertThat(either.isRight()).isFalse(); final Either<Integer, String> either = Either.left(1);
//then
assertThat(either.isRight()).isFalse();
}
@Test
void whenRight_isRight() {
//when
final Either<Integer, String> either = Either.right("1");
//then
assertThat(either.isRight()).isTrue();
}
} }
@Nested
@Test @DisplayName("match()")
void whenRight_isNotLeft() { public class Match {
//when @Test
final Either<Integer, String> either = Either.right("1"); void whenLeft_matchLeft() {
//then //given
assertThat(either.isLeft()).isFalse(); final Either<Integer, String> either = Either.left(1);
//then
either.match(
left -> assertThat(left).isEqualTo(1),
right -> fail("Not a right")
);
}
@Test
void whenRight_matchRight() {
//given
final Either<Integer, String> either = Either.right("1");
//then
either.match(
left -> fail("Not a left"),
right -> assertThat(right).isEqualTo("1")
);
}
} }
@Nested
@Test @DisplayName("mapLeft()")
void whenRight_isRight() { public class MapLeft {
//when @Test
final Either<Integer, String> either = Either.right("1"); void givenLeft_whenMapLeft_thenMap() {
//then //given
assertThat(either.isRight()).isTrue(); final Either<Integer, String> either = Either.left(2);
//when
final Either<Integer, String> result = either.mapLeft(l -> l * 2);
//then
result.match(
left -> assertThat(left).isEqualTo(4),
right -> fail("Not a right")
);
}
@Test
void givenRight_whenMapLeft_thenDoNothing() {
//given
final Either<Integer, String> either = Either.right("2");
//when
final Either<Integer, String> result = either.mapLeft(l -> l * 2);
//then
result.match(
left -> fail("Not a left"),
right -> assertThat(right).isEqualTo("2")
);
}
} }
@Nested
@Test @DisplayName("mapRight()")
void whenLeft_matchLeft() { public class MapRight {
//given @Test
final Either<Integer, String> either = Either.left(1); void givenRight_whenMapRight_thenMap() {
//then //given
either.match( final Either<Integer, String> either = Either.right("2");
left -> assertThat(left).isEqualTo(1), //when
right -> fail("Not a right") final Either<Integer, String> result = either.mapRight(l -> l + "2");
); //then
result.match(
left -> fail("Not a left"),
right -> assertThat(right).isEqualTo("22")
);
}
@Test
void givenLeft_whenMapRight_thenDoNothing() {
//given
final Either<Integer, String> either = Either.left(2);
//when
final Either<Integer, String> result = either.mapRight(r -> r + "x");
//then
result.match(
left -> assertThat(left).isEqualTo(2),
right -> fail("Not a right")
);
}
} }
@Nested
@Test @DisplayName("flatMapLeft()")
void whenRight_matchRight() { public class FlatMapLeft {
//given @Test
final Either<Integer, String> either = Either.right("1"); void givenLeft_whenFlatMapLeft_whenResultLeft_thenIsNewLeft() {
//then //given
either.match( Either<Integer, String> either = Either.left(2);
left -> fail("Not a left"), //when
right -> assertThat(right).isEqualTo("1") Either<Integer, String> result = either.flatMapLeft(l -> Either.left(l * 2));
); //then
result.match(
left -> assertThat(left).isEqualTo(4),
right -> fail("Not a right")
);
}
@Test
void givenLeft_whenFlatMapLeft_whenResultRight_thenIsRight() {
//given
Either<Integer, String> either = Either.left(2);
//when
Either<Integer, String> result = either.flatMapLeft(l -> Either.right("recovered"));
//then
result.match(
left -> fail("Not a left"),
right -> assertThat(right).isEqualTo("recovered")
);
}
@Test
void givenRight_whenFlatMapLeft_thenDoNothing() {
//given
Either<Integer, String> either = Either.right("2");
//when
Either<Integer, String> result = either.flatMapLeft(l -> Either.left(l * 2));
//then
result.match(
left -> fail("Not a left"),
right -> assertThat(right).isEqualTo("2")
);
}
} }
@Nested
@Test @DisplayName("flatMapRight()")
void givenLeft_whenMapLeft_thenMap() { public class FlatMapRight {
//given @Test
final Either<Integer, String> either = Either.left(2); void givenLeft_whenFlatMapRight_thenDoNothing() {
//when //given
final Either<Integer, String> result = either.mapLeft(l -> l * 2); Either<Integer, String> either = Either.left(2);
//then //when
result.match( Either<Integer, String> result = either.flatMapRight(l -> Either.right(l + "2"));
left -> assertThat(left).isEqualTo(4), //then
right -> fail("Not a right") result.match(
); left -> assertThat(left).isEqualTo(2),
right -> fail("Not a right")
);
}
@Test
void givenRight_whenFlatMapRight_whenResultRight_thenIsNewRight() {
//given
Either<Integer, String> either = Either.right("2");
//when
Either<Integer, String> result = either.flatMapRight(l -> Either.right(l + "2"));
//then
result.match(
left -> fail("Not a left"),
right -> assertThat(right).isEqualTo("22")
);
}
@Test
void givenRight_whenFlatMapRight_whenResultLeft_thenIsLeft() {
//given
Either<Integer, String> either = Either.right("2");
//when
Either<Integer, String> result = either.flatMapRight(l -> Either.left(7));
//then
result.match(
left -> assertThat(left).isEqualTo(7),
right -> fail("Not a right")
);
}
} }
@Nested @DisplayName("getLeft") public class GetLeft {
@Test
@DisplayName("")
public void whenLeft_thenGetValue() {
//given
Either<String, Integer> either = Either.left("value");
//when
Optional<String> left = either.getLeft();
//then
assertThat(left).contains("value");
}
@Test @Test
void givenRight_whenMapRight_thenMap() { @DisplayName("")
//given public void whenRight_thenGetEmpty() {
final Either<Integer, String> either = Either.right("2"); //given
//when Either<Integer, String> either = Either.right("value");
final Either<Integer, String> result = either.mapRight(l -> l + "2"); //when
//then Optional<Integer> left = either.getLeft();
result.match( //then
left -> fail("Not a left"), assertThat(left).isEmpty();
right -> assertThat(right).isEqualTo("22") }
);
} }
@Nested @DisplayName("getRight") public class GetRight {
@Test
@DisplayName("")
public void whenLeft_thenGetEmpty() {
//given
Either<String, Integer> either = Either.left("value");
//when
Optional<Integer> left = either.getRight();
//then
assertThat(left).isEmpty();
}
@Test @Test
void givenLeft_whenMapRight_thenDoNothing() { @DisplayName("")
//given public void whenRight_thenGetValue() {
final Either<Integer, String> either = Either.left(2); //given
//when Either<Integer, String> either = Either.right("value");
final Either<Integer, String> result = either.mapRight(r -> r + "x"); //when
//then Optional<String> left = either.getRight();
result.match( //then
left -> assertThat(left).isEqualTo(2), assertThat(left).contains("value");
right -> fail("Not a right") }
);
}
@Test
void givenRight_whenMapLeft_thenDoNothing() {
//given
final Either<Integer, String> either = Either.right("2");
//when
final Either<Integer, String> result = either.mapLeft(l -> l * 2);
//then
result.match(
left -> fail("Not a left"),
right -> assertThat(right).isEqualTo("2")
);
} }
// @Nested
// @DisplayName("compose()")
// public class Compose {
//
// BiFunction<String, String, Either<String, Integer>> leftToLeftComposer =
// (la, lb) -> Either.left(la + lb);
//
// BiFunction<String, String, Either<String, Integer>> leftToRightComposer =
// (la, lb) -> Either.right(Integer.parseInt(la) + Integer.parseInt(lb));
//
// BiFunction<Integer, Integer, Either<String, Integer>> rightToLeftComposer =
// (la, lb) -> Either.left("" + la + lb);
//
// BiFunction<Integer, Integer, Either<String, Integer>> rightToRightComposer =
// (la, lb) -> Either.right(la + lb);
//
// @Test
// public void givenRightAndRight_whenComposeRight_then() {
// //given
// Either<String, Integer> ea = Either.right(3);
// Either<String, Integer> eb = Either.right(7);
// //when
// Either<String, Integer> result = ea.compose(eb,
// leftToRightComposer,
// rightToRightComposer);
// //then
// result.match(
// left -> fail("Not a left"),
// right -> assertThat(right).isEqualTo(10)
// );
// }
//
// @Test
// public void givenRightAndRight_whenComposeLeft_then() {
// //given
// Either<String, Integer> ea = Either.right(3);
// Either<String, Integer> eb = Either.right(7);
// //when
// Either<String, Integer> result = ea.compose(eb,
// leftToLeftComposer,
// rightToLeftComposer);
// //then
// result.match(
// left -> assertThat(left).isEqualTo("a+b"),
// right -> fail("Not a right")
// );
// }
// // compose left with left
// @Test
// public void givenLeftAndLeft_whenComposeRight_then() {
// //given
// Either<String, Integer> ea = Either.left("3");
// Either<String, Integer> eb = Either.left("7");
// //when
// Either<String, Integer> result = ea.compose(eb,
// leftToRightComposer,
// rightToRightComposer);
// //then
// result.match(
// left -> fail("Not a left"),
// right -> assertThat(right).isEqualTo(10)
// );
// }
// @Test
// public void givenLeftAndLeft_whenComposeLeft_then() {
// //given
// Either<String, Integer> ea = Either.left("3");
// Either<String, Integer> eb = Either.left("7");
// //when
// Either<String, Integer> result = ea.compose(eb,
// leftToLeftComposer,
// rightToLeftComposer);
// //then
// result.match(
// left -> fail("Not a left"),
// right -> assertThat(right).isEqualTo(10)
// );
// }
// // compose left with right
// @Test
// public void givenLeftAndRight_whenComposeRight_then() {
// //given
// Either<String, Integer> ea = Either.left("3");
// Either<String, Integer> eb = Either.right(7);
// //when
// Either<String, Integer> result = ea.compose(eb,
// leftToRightComposer,
// rightToRightComposer);
// //then
// result.match(
// left -> fail("Not a left"),
// right -> assertThat(right).isEqualTo(10)
// );
// }
// @Test
// public void givenLeftAndRight_whenComposeLeft_then() {
// //given
// Either<String, Integer> ea = Either.left("3");
// Either<String, Integer> eb = Either.right(7);
// //when
// Either<String, Integer> result = ea.compose(eb,
// leftToLeftComposer,
// rightToLeftComposer);
// //then
// result.match(
// left -> fail("Not a left"),
// right -> assertThat(right).isEqualTo(10)
// );
// }
// // compose right with left
// @Test
// public void givenRightAndLeft_whenComposeRight_then() {
// //given
// Either<String, Integer> ea = Either.right(3);
// Either<String, Integer> eb = Either.left("7");
// //when
// Either<String, Integer> result = ea.compose(eb,
// leftToRightComposer,
// rightToRightComposer);
// //then
// result.match(
// left -> fail("Not a left"),
// right -> assertThat(right).isEqualTo(10)
// );
// }
// @Test
// public void givenRightAndLeft_whenComposeLeft_then() {
// //given
// Either<String, Integer> ea = Either.right(3);
// Either<String, Integer> eb = Either.left("7");
// //when
// Either<String, Integer> result = ea.compose(eb,
// leftToLeftComposer,
// rightToLeftComposer);
// //then
// result.match(
// left -> fail("Not a left"),
// right -> assertThat(right).isEqualTo(10)
// );
// }
// }
} }

View file

@ -86,7 +86,7 @@ class MutableTreeTest implements WithAssertions {
asList( asList(
MutableTree.leaf(sid2), MutableTree.leaf(sid2),
MutableTree.leaf(sid3))); MutableTree.leaf(sid3)));
assertThat(result).isEqualToComparingFieldByFieldRecursively(expectedTree); assertThat(result).usingRecursiveComparison().isEqualTo(expectedTree);
} }
@Test @Test

View file

@ -65,7 +65,7 @@ class TreeBuilderTest {
final Tree<Node> result = rootBuilder.build(); final Tree<Node> result = rootBuilder.build();
//then //then
assertThat(result.count()).isEqualTo(3); assertThat(result.count()).isEqualTo(3);
assertThat(result).isEqualToComparingFieldByFieldRecursively( assertThat(result).usingRecursiveComparison().isEqualTo(
MutableTree.of(rootNode, Collections.singleton( MutableTree.of(rootNode, Collections.singleton(
MutableTree.of(childNode, Collections.singleton( MutableTree.of(childNode, Collections.singleton(
MutableTree.leaf(grandchildNode)))))); MutableTree.leaf(grandchildNode))))));
@ -86,7 +86,7 @@ class TreeBuilderTest {
.build(); .build();
//then //then
assertThat(result.count()).isEqualTo(4); assertThat(result.count()).isEqualTo(4);
assertThat(result).isEqualToComparingFieldByFieldRecursively( assertThat(result).usingRecursiveComparison().isEqualTo(
MutableTree.of(rootNode, asList( MutableTree.of(rootNode, asList(
MutableTree.leaf(child1Node), MutableTree.leaf(child1Node),
MutableTree.leaf(child2Node), MutableTree.leaf(child2Node),