Add Maybe, Just, Nothing

This commit is contained in:
Paul Campbell 2017-12-10 16:11:12 +00:00
parent 41f9227afd
commit e0eb0614c3
6 changed files with 490 additions and 0 deletions

View file

@ -5,6 +5,7 @@ CHANGELOG
----- -----
* Restore public access for `TypeAlias.getValue()` * Restore public access for `TypeAlias.getValue()`
* Add `Maybe`, `Just`, `Nothing`
0.3.0 0.3.0
----- -----

View file

@ -41,3 +41,37 @@ void foo(final Goal goal) {
System.out.println("The goal is " + goal.getValue()); System.out.println("The goal is " + goal.getValue());
} }
``` ```
### Maybe (Just & Nothing)
```java
assertThat(Maybe.maybe(null)).isEqualTo(Maybe.nothing());
assertThat(Maybe.maybe(1)).isEqualTo(Maybe.just(1));
assertThat(Maybe.nothing()
.orElseGet(() -> 1)).isEqualTo(1);
assertThat(Maybe.just(1)
.orElseGet(() -> 2)).isEqualTo(1);
assertThat(Maybe.nothing()
.orElse(1)).isEqualTo(1);
assertThat(Maybe.just(1)
.orElse(2)).isEqualTo(1);
assertThat(Maybe.just(1)
.filter(v -> v > 2)).isEqualTo(Maybe.nothing());
assertThat(Maybe.just(3)
.filter(v -> v > 2)).isEqualTo(Maybe.just(3));
assertThat(Maybe.just(1)
.toOptional()).isEqualTo(Optional.of(1));
assertThat(Maybe.nothing()
.toOptional()).isEqualTo(Optional.empty());
assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(Maybe.just(1));
assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing());
final AtomicInteger reference = new AtomicInteger(0);
assertThat(Maybe.just(1).peek(reference::set)).isEqualTo(Maybe.just(1));
assertThat(reference).hasValue(1);
assertThat(Maybe.nothing().peek(v -> reference.incrementAndGet())).isEqualTo(Maybe.nothing());
assertThat(reference).hasValue(1);
assertThatCode(() -> Maybe.just(1).orElseThrow(IllegalStateException::new))
.doesNotThrowAnyException();
assertThatThrownBy(() -> Maybe.nothing().orElseThrow(IllegalStateException::new))
.isInstanceOf(IllegalStateException.class);
```

View file

@ -0,0 +1,94 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 Paul Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package net.kemitix.mon;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* A Maybe where a value is present.
*
* @param <T> the type of the content
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public final class Just<T> implements Maybe<T> {
private final T value;
@Override
public <R> Maybe<?> map(final Function<T, R> f) {
return new Just<>(f.apply(value));
}
@Override
public boolean equals(final Object other) {
return other instanceof Just && Objects.equals(this.value, ((Just) other).value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
@Override
public T orElseGet(final Supplier<T> supplier) {
return value;
}
@Override
public T orElse(final T otherValue) {
return this.value;
}
@Override
public Maybe<T> filter(final Predicate<T> predicate) {
if (predicate.test(value)) {
return this;
}
return Maybe.nothing();
}
@Override
public Optional<T> toOptional() {
return Optional.of(value);
}
@Override
public Maybe<T> peek(final Consumer<T> consumer) {
consumer.accept(value);
return this;
}
@Override
public void orElseThrow(final Supplier<Exception> e) {
// do not throw
}
}

View file

@ -0,0 +1,145 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 Paul Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package net.kemitix.mon;
import lombok.NonNull;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* A value that may or may not be present.
*
* @param <T> the type of the content of the Just
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public interface Maybe<T> extends Functor<T, Maybe<?>> {
/**
* Create a Maybe for the value that is present.
*
* @param value the value, not null
* @param <T> the type of the value
*
* @return a Maybe of the value
*/
static <T> Maybe<T> just(@NonNull final T value) {
return new Just<>(value);
}
/**
* Create a Maybe for a lack of a value.
*
* @param <T> the type of the missing value
*
* @return an empty Maybe
*/
@SuppressWarnings("unchecked")
static <T> Maybe<T> nothing() {
return (Maybe<T>) Nothing.INSTANCE;
}
/**
* Create a Maybe for the value that may or may not be present.
*
* @param value the value, may be null
* @param <T> the type of the value
*
* @return a Maybe, either a Just, or Nothing if value is null
*/
static <T> Maybe<T> maybe(final T value) {
if (value == null) {
return nothing();
}
return just(value);
}
/**
* Create a Maybe from an {@link Optional}.
*
* @param optional the Optional
* @param <T> the type of the Optional
*
* @return a Maybe
*/
static <T> Maybe<T> fromOptional(final Optional<T> optional) {
return optional.map(Maybe::maybe)
.orElse(nothing());
}
/**
* Provide a value to use when Maybe is Nothing.
*
* @param supplier supplier for an alternate value
*
* @return a Maybe
*/
T orElseGet(Supplier<T> supplier);
/**
* A value to use when Maybe is Nothing.
*
* @param otherValue an alternate value
*
* @return a Maybe
*/
T orElse(T otherValue);
/**
* Filter a Maybe by the predicate, replacing with Nothing when it fails.
*
* @param predicate the test
*
* @return the Maybe, or Nothing if the test returns false
*/
Maybe<T> filter(Predicate<T> predicate);
/**
* Convert the Maybe to an {@link Optional}.
*
* @return an Optional containing a value for a Just, or empty for a Nothing
*/
Optional<T> toOptional();
/**
* Provide the value within the Maybe, if it exists, to the Supplier, and returns the Maybe.
*
* @param consumer the Consumer to the value if present
*
* @return the Maybe
*/
Maybe<T> peek(Consumer<T> consumer);
/**
* Throw the exception if the Maybe is a Nothing.
*
* @param e the exception to throw
*
* @throws Exception if the Maybe is a Nothing
*/
@SuppressWarnings("illegalthrows")
void orElseThrow(Supplier<Exception> e) throws Exception;
}

View file

@ -0,0 +1,75 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 Paul Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package net.kemitix.mon;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* A Maybe where no value is present.
*
* @param <T> the type of the missing content
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public final class Nothing<T> implements Maybe<T> {
protected static final Maybe<?> INSTANCE = new Nothing<>();
@Override
public <R> Maybe<?> map(final Function<T, R> f) {
return this;
}
@Override
public T orElseGet(final Supplier<T> supplier) {
return supplier.get();
}
@Override
public T orElse(final T otherValue) {
return otherValue;
}
@Override
public Maybe<T> filter(final Predicate<T> predicate) {
return this;
}
@Override
public Optional<T> toOptional() {
return Optional.empty();
}
@Override
public Maybe<T> peek(final Consumer<T> consumer) {
return this;
}
@Override
public void orElseThrow(final Supplier<Exception> e) throws Exception {
throw e.get();
}
}

View file

@ -0,0 +1,141 @@
package net.kemitix.mon;
import org.junit.Test;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import static net.kemitix.mon.Maybe.just;
import static net.kemitix.mon.Maybe.maybe;
import static net.kemitix.mon.Maybe.nothing;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class MaybeTest {
private static <T> Predicate<T> eq(final T value) {
return v -> Objects.equals(value, v);
}
@Test
public void documentation() {
assertThat(Maybe.maybe(null)).isEqualTo(Maybe.nothing());
assertThat(Maybe.maybe(1)).isEqualTo(Maybe.just(1));
assertThat(Maybe.nothing()
.orElseGet(() -> 1)).isEqualTo(1);
assertThat(Maybe.just(1)
.orElseGet(() -> 2)).isEqualTo(1);
assertThat(Maybe.nothing()
.orElse(1)).isEqualTo(1);
assertThat(Maybe.just(1)
.orElse(2)).isEqualTo(1);
assertThat(Maybe.just(1)
.filter(v -> v > 2)).isEqualTo(Maybe.nothing());
assertThat(Maybe.just(3)
.filter(v -> v > 2)).isEqualTo(Maybe.just(3));
assertThat(Maybe.just(1)
.toOptional()).isEqualTo(Optional.of(1));
assertThat(Maybe.nothing()
.toOptional()).isEqualTo(Optional.empty());
assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(Maybe.just(1));
assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing());
final AtomicInteger reference = new AtomicInteger(0);
assertThat(Maybe.just(1)
.peek(reference::set)).isEqualTo(Maybe.just(1));
assertThat(reference).hasValue(1);
assertThat(Maybe.nothing()
.peek(v -> reference.incrementAndGet())).isEqualTo(Maybe.nothing());
assertThat(reference).hasValue(1);
assertThatCode(() -> Maybe.just(1)
.orElseThrow(IllegalStateException::new)).doesNotThrowAnyException();
assertThatThrownBy(() -> Maybe.nothing()
.orElseThrow(IllegalStateException::new)).isInstanceOf(
IllegalStateException.class);
}
@Test
public void justMustBeNonNull() {
assertThatNullPointerException().isThrownBy(() -> just(null))
.withMessage("value");
}
@Test
public void nothingReusesTheSameInstance() {
assertThat(nothing()).isSameAs(nothing());
}
@Test
public void maybeAllowsNull() {
assertThat(just(1)).isEqualTo(maybe(1));
assertThat(nothing()).isEqualTo(maybe(null));
}
@Test
public void map() {
assertThat(just(1).map(v -> v + 1)).isEqualTo(just(2));
assertThat(nothing().map(v -> v)).isEqualTo(nothing());
}
@Test
public void testHashCode() {
assertThat(just(1).hashCode()).isEqualTo(Objects.hashCode(1));
}
@Test
public void orElseGet() {
assertThat(just(1).orElseGet(() -> -1)).isEqualTo(1);
assertThat(nothing().orElseGet(() -> -1)).isEqualTo(-1);
}
@Test
public void orElse() {
assertThat(just(1).orElse(-1)).isEqualTo(1);
assertThat(nothing().orElse(-1)).isEqualTo(-1);
}
@Test
public void filter() {
assertThat(just(1).filter(eq(1))).isEqualTo(just(1));
assertThat(just(1).filter(eq(0))).isEqualTo(nothing());
assertThat(nothing().filter(eq(1))).isEqualTo(nothing());
}
@Test
public void toOptional() {
assertThat(just(1).toOptional()).isEqualTo(Optional.of(1));
assertThat(Maybe.nothing()
.toOptional()).isEqualTo(Optional.empty());
}
@Test
public void fromOptional() {
assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(just(1));
assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing());
}
@Test
public void peek() {
final AtomicInteger ref = new AtomicInteger(0);
assertThat(just(1).peek(x -> ref.incrementAndGet())).isEqualTo(just(1));
assertThat(ref.get()).isEqualTo(1);
assertThat(nothing().peek(x -> ref.incrementAndGet())).isEqualTo(nothing());
assertThat(ref.get()).isEqualTo(1);
}
@Test
public void justOrThrow() {
assertThatCode(() -> just(1).orElseThrow(IllegalStateException::new)).doesNotThrowAnyException();
}
@Test
public void nothingOrThrow() {
assertThatThrownBy(() -> nothing().orElseThrow(IllegalStateException::new)).isInstanceOf(
IllegalStateException.class);
}
}