From 0c178e88215feaa91a082008117b17a768e94a74 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 16 Sep 2017 22:09:10 +0100 Subject: [PATCH] Mon implements Functor --- src/main/java/net/kemitix/mon/Mon.java | 142 ++++++++++++++++++++ src/test/java/net/kemitix/mon/MonTest.java | 147 +++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 src/main/java/net/kemitix/mon/Mon.java create mode 100644 src/test/java/net/kemitix/mon/MonTest.java diff --git a/src/main/java/net/kemitix/mon/Mon.java b/src/main/java/net/kemitix/mon/Mon.java new file mode 100644 index 0000000..eff8554 --- /dev/null +++ b/src/main/java/net/kemitix/mon/Mon.java @@ -0,0 +1,142 @@ +/** + * 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.NonNull; +import lombok.RequiredArgsConstructor; + +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * An almost-monad type-alias. + * + * @param the type of the alias + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public class Mon implements Functor { + + /** + * The value. + */ + private final T value; + + /** + * Create a factory for creating validated instances. + * + *

The value can never be null.

+ * + * @param validator the validator function + * @param onValid the function to create when valid + * @param onInvalid the function to create when invalid + * @param the type of the value + * @param the type of the factory output + * + * @return a function to apply to values to alias + */ + public static Function factory( + @NonNull final Predicate validator, + @NonNull final Function, R> onValid, + @NonNull final Supplier onInvalid + ) { + return v -> { + if (v != null && validator.test(v)) { + return onValid.apply(Mon.of(v)); + } + return onInvalid.get(); + }; + } + + /** + * Create a new Mon for the value. + * + * @param v the value + * @param the type of the value + * + * @return a Mon containing the value + */ + public static Mon of(@NonNull final T v) { + return new Mon<>(v); + } + + /** + * Applies the function to the value within the Mon, returning a Mon containing the result. + * + * @param f the function to apply + * @param the type of the result of the function + * + * @return a Mon containing the result of the function {@code f} to the value + */ + @Override + public Mon map(final Function f) { + return Mon.of(f.apply(value)); + } + + /** + * Returns a Mon consisting of the results of replacing the content of this Mon with the contents of a mapped Mon + * produced by applying the provided mapping function to the content of the Mon. + * + * @param f the mapping function the produces a Mon + * @param the type of the result of the mapping function + * + * @return a Mon containing the result of the function + */ + public Mon flatMap(final Function> f) { + return f.apply(value); + } + + /** + * The hashcode. + * + * @return the hashcode + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * equals. + * + * @param o the object to compare to + * + * @return true if they are the same + */ + @Override + @SuppressWarnings("npathcomplexity") + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Mon mon = (Mon) o; + + return value.equals(mon.value); + } +} diff --git a/src/test/java/net/kemitix/mon/MonTest.java b/src/test/java/net/kemitix/mon/MonTest.java new file mode 100644 index 0000000..91b51f0 --- /dev/null +++ b/src/test/java/net/kemitix/mon/MonTest.java @@ -0,0 +1,147 @@ +package net.kemitix.mon; + +import org.junit.Test; + +import java.util.Optional; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNullPointerException; + +public class MonTest { + + @Test + public void canCreateAndMapCanGetValue() { + //when + final Mon wrap = Mon.of("test"); + //then + wrap.map(value -> assertThat(value).isEqualTo("test")); + } + + @Test + public void canMap() { + //given + final Mon wrap = Mon.of("test"); + //when + final Mon updated = wrap.map(a -> a + " more"); + //then + updated.map(value -> assertThat(value).isEqualTo("test more")); + } + + @Test + public void canMapInstance() { + //given + final Mon wrap = Mon.of("test"); + //when + final Mon result = wrap.map(String::length); + //then + result.map(value -> assertThat(value).isEqualTo(4)); + } + + @Test + public void createWithValidatorAndContinuations() { + //given + final Function>> factory = + Mon.factory( + v -> v.length() <= 10, + Optional::of, + Optional::empty + ); + //when + final Optional> shortAndValid = factory.apply("value okay"); + final Optional> longAndInvalid = factory.apply("value is too long"); + //then + assertThat(shortAndValid).isNotEmpty(); + shortAndValid.ifPresent(valid -> valid.map(value -> assertThat(value).contains("value okay"))); + assertThat(longAndInvalid).isEmpty(); + } + + @Test + + public void canCompare() { + //given + final Mon one = Mon.of("test"); + final Mon same = Mon.of("test"); + final Mon other = Mon.of("other"); + //then + assertThat(one).isEqualTo(same); + assertThat(one).isNotEqualTo(other); + } + + @Test + public void canFlatMap() { + //given + final Mon wrap = Mon.of("test"); + //when + final Mon> nonFlatMapped = wrap.map(Mon::of); + final Mon result = wrap.flatMap(Mon::of); + //then + result.map(value -> assertThat(value).isEqualTo("test")); + nonFlatMapped.map(inner -> assertThat(result).isEqualTo(inner)); + } + + @Test + public void ofRequiresNonNull() { + assertThatNullPointerException().isThrownBy(() -> Mon.of(null)); + } + + @Test + public void factoryRequiresValidator() { + assertThatNullPointerException().isThrownBy( + () -> Mon.factory(null, Optional::of, Optional::empty)) + .withMessage("validator"); + } + + @Test + public void factoryRequiresOnValid() { + assertThatNullPointerException().isThrownBy( + () -> Mon.factory(v -> true, null, Optional::empty)) + .withMessage("onValid"); + } + + @Test + public void factoryRequiresOnInvalid() { + assertThatNullPointerException().isThrownBy( + () -> Mon.factory(v -> true, Optional::of, null)) + .withMessage("onInvalid"); + } + + @Test + public void shouldGetInvalidResultWhenFactoryApplyWithNull() { + //given + final Function> factory = Mon.factory(v -> true, Optional::of, Optional::empty); + //when + final Optional result = factory.apply(null); + //then + assertThat(result).isEmpty(); + } + + @Test + public void shouldHaveHashCodeBasedOnContent() { + //given + final int hashOfOne = Mon.of("one") + .hashCode(); + final int hashOfTwo = Mon.of("two") + .hashCode(); + final int otherHashOfAOne = Mon.of("one") + .hashCode(); + //then + assertThat(hashOfOne).isNotEqualTo(hashOfTwo); + assertThat(hashOfOne).isEqualTo(otherHashOfAOne); + } + + @Test + public void shouldHaveEquals() { + //given + final Mon one = Mon.of("one"); + final Mon two = Mon.of("two"); + final Mon otherOne = Mon.of("one"); + final Integer notAMon = 1; + //then + assertThat(one).isEqualTo(one); + assertThat(one).isNotEqualTo(two); + assertThat(one).isEqualTo(otherOne); + assertThat(one).isNotEqualTo(notAMon); + assertThat(one).isNotEqualTo(null); + } +}