Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(either): Missing flatmaps and map operations #218

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/main/java/io/github/joselion/maybe/util/Either.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,75 @@ default <T> Either<L, T> mapRight(final Function<R, T> mapper) {
);
}

/**
* Shortcut method which does a {@link #mapLeft(Function)} and a
* {@link #mapRight(Function)} in a single operation. The first argument
* maps the left value if present. Otherwise, the second argument maps the
* right value.
*
* @param <T> the type the left value will be mapped to
* @param <S> the type the right value will be mapped to
* @param leftMapper a function that receives the left value and returns another
* @param rigthMapper a function that receives the right value and returns another
* @return an {@code Either} instance with the mapped left or right value
*/
default <T, S> Either<T, S> map(final Function<L, T> leftMapper, final Function<R, S> rigthMapper) {
return unwrap(
left -> Either.ofLeft(leftMapper.apply(left)),
right -> Either.ofRight(rigthMapper.apply(right))
);
}

/**
* Map the {@code Left} value to another if present. Does nothing otherwise.
*
* This method is similar to {@link #mapLeft(Function)}, but the
* mapping function can return another {@code Either} without wrapping the
* left value within an additional {@code Either}.
*
* @param <T> the type the left value will be mapped to
* @param mapper a function that receives the left value and returns an {@code Either}
* @return an {@code Either} instance with the mapped left value
*/
default <T> Either<T, R> flatMapLeft(final Function<L, Either<T, R>> mapper) {
return unwrap(mapper, Either::ofRight);
}

/**
* Map the {@code Right} value to another if present. Does nothing otherwise.
*
* This method is similar to {@link #mapRight(Function)}, but the
* mapping function can return another {@code Either} without wrapping the
* right value within an additional {@code Either}.
*
* @param <T> the type the right value will be mapped to
* @param mapper a function that receives the right value and returns an {@code Either}
* @return an {@code Either} instance with the mapped right value
*/
default <T> Either<L, T> flatMapRight(final Function<R, Either<L, T>> mapper) {
return unwrap(Either::ofLeft, mapper);
}

/**
* Shortcut method which does a {@link #flatMapLeft(Function)} and a
* {@link #flatMapRight(Function)} in a single operation. The first argument
* maps the left value if present. Otherwise, the second argument maps the
* right value. In both cases, the mapped left/right values are never wrapped
* within an additional {@code Either}.
*
* @param <T> the type the left value will be mapped to
* @param <S> the type the right value will be mapped to
* @param leftMapper a function that receives the left value and returns an {@code Either}
* @param rigthMapper a function that receives the right value and returns an {@code Either}
* @return an {@code Either} instance with the mapped left or right value
*/
default <T, S> Either<T, S> flatMap(
final Function<L, Either<T, S>> leftMapper,
final Function<R, Either<T, S>> rigthMapper
) {
return unwrap(leftMapper, rigthMapper);
}

/**
* Terminal operator. Returns the {@code Left} value if present. Otherwise,
* it returns the provided fallback value.
Expand Down
69 changes: 69 additions & 0 deletions src/main/java17/io/github/joselion/maybe/util/Either.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,75 @@ default <T> Either<L, T> mapRight(final Function<R, T> mapper) {
);
}

/**
* Shortcut method which does a {@link #mapLeft(Function)} and a
* {@link #mapRight(Function)} in a single operation. The first argument
* maps the left value if present. Otherwise, the second argument maps the
* right value.
*
* @param <T> the type the left value will be mapped to
* @param <S> the type the right value will be mapped to
* @param leftMapper a function that receives the left value and returns another
* @param rigthMapper a function that receives the right value and returns another
* @return an {@code Either} instance with the mapped left or right value
*/
default <T, S> Either<T, S> map(final Function<L, T> leftMapper, final Function<R, S> rigthMapper) {
return unwrap(
left -> Either.ofLeft(leftMapper.apply(left)),
right -> Either.ofRight(rigthMapper.apply(right))
);
}

/**
* Map the {@code Left} value to another if present. Does nothing otherwise.
*
* This method is similar to {@link #mapLeft(Function)}, but the
* mapping function can return another {@code Either} without wrapping the
* left value within an additional {@code Either}.
*
* @param <T> the type the left value will be mapped to
* @param mapper a function that receives the left value and returns an {@code Either}
* @return an {@code Either} instance with the mapped left value
*/
default <T> Either<T, R> flatMapLeft(final Function<L, Either<T, R>> mapper) {
return unwrap(mapper, Either::ofRight);
}

/**
* Map the {@code Right} value to another if present. Does nothing otherwise.
*
* This method is similar to {@link #mapRight(Function)}, but the
* mapping function can return another {@code Either} without wrapping the
* right value within an additional {@code Either}.
*
* @param <T> the type the right value will be mapped to
* @param mapper a function that receives the right value and returns an {@code Either}
* @return an {@code Either} instance with the mapped right value
*/
default <T> Either<L, T> flatMapRight(final Function<R, Either<L, T>> mapper) {
return unwrap(Either::ofLeft, mapper);
}

/**
* Shortcut method which does a {@link #flatMapLeft(Function)} and a
* {@link #flatMapRight(Function)} in a single operation. The first argument
* maps the left value if present. Otherwise, the second argument maps the
* right value. In both cases, the mapped left/right values are never wrapped
* within an additional {@code Either}.
*
* @param <T> the type the left value will be mapped to
* @param <S> the type the right value will be mapped to
* @param leftMapper a function that receives the left value and returns an {@code Either}
* @param rigthMapper a function that receives the right value and returns an {@code Either}
* @return an {@code Either} instance with the mapped left or right value
*/
default <T, S> Either<T, S> flatMap(
final Function<L, Either<T, S>> leftMapper,
final Function<R, Either<T, S>> rigthMapper
) {
return unwrap(leftMapper, rigthMapper);
}

/**
* Terminal operator. Returns the {@code Left} value if present. Otherwise,
* it returns the provided fallback value.
Expand Down
30 changes: 14 additions & 16 deletions src/test/java/io/github/joselion/maybe/EffectHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
import java.nio.file.AccessDeniedException;
import java.nio.file.FileSystemException;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand All @@ -35,7 +33,7 @@
@Nested class doOnSuccess {
@Nested class when_the_value_is_present {
@Test void calls_the_effect_callback() {
final var runnableSpy = Spy.<Runnable>lambda(() -> { });
final var runnableSpy = Spy.runnable(() -> { });

Maybe.fromEffect(noOp).doOnSuccess(runnableSpy);

Expand All @@ -45,7 +43,7 @@

@Nested class when_the_value_is_not_present {
@Test void never_calls_the_effect_callback() {
final var runnableSpy = Spy.<Runnable>lambda(() -> { });
final var runnableSpy = Spy.runnable(() -> { });

Maybe.fromEffect(throwingOp).doOnSuccess(runnableSpy);

Expand All @@ -59,7 +57,7 @@
@Nested class and_the_error_type_is_provided {
@Nested class and_the_error_is_an_instance_of_the_provided_type {
@Test void calls_the_effect_callback() {
final var consumerSpy = Spy.<Consumer<FileSystemException>>lambda(error -> { });
final var consumerSpy = Spy.<FileSystemException>consumer(error -> { });

Maybe.fromEffect(throwingOp)
.doOnError(FileSystemException.class, consumerSpy);
Expand All @@ -70,7 +68,7 @@

@Nested class and_the_error_is_not_an_instance_of_the_provided_type {
@Test void never_calls_the_effect_callback() {
final var consumerSpy = Spy.<Consumer<RuntimeException>>lambda(error -> { });
final var consumerSpy = Spy.<RuntimeException>consumer(error -> { });

Maybe.fromEffect(throwingOp)
.doOnError(RuntimeException.class, consumerSpy);
Expand All @@ -82,7 +80,7 @@

@Nested class and_the_error_type_is_not_provided {
@Test void calls_the_effect_callback() {
final var consumerSpy = Spy.<Consumer<FileSystemException>>lambda(error -> { });
final var consumerSpy = Spy.<FileSystemException>consumer(error -> { });

Maybe.fromEffect(throwingOp)
.doOnError(consumerSpy);
Expand All @@ -94,7 +92,7 @@

@Nested class when_the_error_is_not_present {
@Test void never_calls_the_effect_callback() {
final var cunsumerSpy = Spy.<Consumer<RuntimeException>>lambda(error -> { });
final var cunsumerSpy = Spy.<RuntimeException>consumer(error -> { });

Maybe.fromEffect(noOp)
.doOnError(RuntimeException.class, cunsumerSpy)
Expand All @@ -110,7 +108,7 @@
@Nested class and_the_error_type_is_provided {
@Nested class and_the_error_is_an_instance_of_the_provided_type {
@Test void calls_the_handler_function() {
final var consumerSpy = Spy.<Consumer<FileSystemException>>lambda(e -> { });
final var consumerSpy = Spy.<FileSystemException>consumer(e -> { });
final var handler = Maybe.fromEffect(throwingOp)
.catchError(FileSystemException.class, consumerSpy);

Expand All @@ -122,7 +120,7 @@

@Nested class and_the_error_is_not_an_instance_of_the_provided_type {
@Test void never_calls_the_handler_function() {
final var consumerSpy = Spy.<Consumer<AccessDeniedException>>lambda(e -> { });
final var consumerSpy = Spy.<AccessDeniedException>consumer(e -> { });
final var handler = Maybe.fromEffect(throwingOp)
.catchError(AccessDeniedException.class, consumerSpy);

Expand All @@ -135,7 +133,7 @@

@Nested class and_the_error_type_is_not_provided {
@Test void calls_the_handler_function() {
final var consumerSpy = Spy.<Consumer<FileSystemException>>lambda(e -> { });
final var consumerSpy = Spy.<FileSystemException>consumer(e -> { });
final var handler = Maybe.fromEffect(throwingOp)
.catchError(consumerSpy);

Expand All @@ -148,7 +146,7 @@

@Nested class when_the_error_is_not_present {
@Test void never_calls_the_handler_function() {
final var consumerSpy = Spy.<Consumer<RuntimeException>>lambda(e -> { });
final var consumerSpy = Spy.<RuntimeException>consumer(e -> { });
final var handlers = List.of(
Maybe.fromEffect(noOp).catchError(RuntimeException.class, consumerSpy),
Maybe.fromEffect(noOp).catchError(consumerSpy)
Expand Down Expand Up @@ -224,7 +222,7 @@
@Nested class orElse {
@Nested class when_the_error_is_present {
@Test void calls_the_effect_callback() {
final var consumerSpy = Spy.<Consumer<FileSystemException>>lambda(e -> { });
final var consumerSpy = Spy.<FileSystemException>consumer(e -> { });
final var handler = Maybe.fromEffect(throwingOp);

handler.orElse(consumerSpy);
Expand All @@ -235,7 +233,7 @@

@Nested class when_the_error_is_not_present {
@Test void never_calls_the_effect_callback() {
final var consumerSpy = Spy.<Consumer<RuntimeException>>lambda(e -> { });
final var consumerSpy = Spy.<RuntimeException>consumer(e -> { });
final var handler = Maybe.fromEffect(noOp);

handler.orElse(consumerSpy);
Expand All @@ -249,7 +247,7 @@
@Nested class when_the_error_is_present {
@Test void throws_the_error() {
final var anotherError = new RuntimeException("OTHER");
final var functionSpy = Spy.<Function<FileSystemException, RuntimeException>>lambda(err -> anotherError);
final var functionSpy = Spy.function((FileSystemException err) -> anotherError);
final var handler = Maybe.fromEffect(throwingOp);

assertThatThrownBy(handler::orThrow).isEqualTo(FAIL_EXCEPTION);
Expand All @@ -261,7 +259,7 @@

@Nested class when_the_error_is_not_present {
@Test void no_exception_is_thrown() {
final var functionSpy = Spy.<Function<RuntimeException, FileSystemException>>lambda(err -> FAIL_EXCEPTION);
final var functionSpy = Spy.function((RuntimeException err) -> FAIL_EXCEPTION);
final var handler = Maybe.fromEffect(noOp);

assertThatCode(handler::orThrow).doesNotThrowAnyException();
Expand Down
Loading