Mon<T> implements Functor<T>

This commit is contained in:
Paul Campbell 2017-09-16 22:09:10 +01:00
parent b1066bb4b0
commit 0c178e8821
2 changed files with 289 additions and 0 deletions

View file

@ -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 <T> the type of the alias
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class Mon<T> implements Functor<T> {
/**
* The value.
*/
private final T value;
/**
* Create a factory for creating validated instances.
*
* <p>The value can never be null.</p>
*
* @param validator the validator function
* @param onValid the function to create when valid
* @param onInvalid the function to create when invalid
* @param <T> the type of the value
* @param <R> the type of the factory output
*
* @return a function to apply to values to alias
*/
public static <T, R> Function<T, R> factory(
@NonNull final Predicate<T> validator,
@NonNull final Function<Mon<T>, R> onValid,
@NonNull final Supplier<R> 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 <T> the type of the value
*
* @return a Mon containing the value
*/
public static <T> Mon<T> 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 <R> the type of the result of the function
*
* @return a Mon containing the result of the function {@code f} to the value
*/
@Override
public <R> Mon<R> map(final Function<T, R> 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 <R> the type of the result of the mapping function
*
* @return a Mon containing the result of the function
*/
public <R> Mon<R> flatMap(final Function<T, Mon<R>> 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);
}
}

View file

@ -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<String> wrap = Mon.of("test");
//then
wrap.map(value -> assertThat(value).isEqualTo("test"));
}
@Test
public void canMap() {
//given
final Mon<String> wrap = Mon.of("test");
//when
final Mon<String> updated = wrap.map(a -> a + " more");
//then
updated.map(value -> assertThat(value).isEqualTo("test more"));
}
@Test
public void canMapInstance() {
//given
final Mon<String> wrap = Mon.of("test");
//when
final Mon<Integer> result = wrap.map(String::length);
//then
result.map(value -> assertThat(value).isEqualTo(4));
}
@Test
public void createWithValidatorAndContinuations() {
//given
final Function<String, Optional<Mon<String>>> factory =
Mon.factory(
v -> v.length() <= 10,
Optional::of,
Optional::empty
);
//when
final Optional<Mon<String>> shortAndValid = factory.apply("value okay");
final Optional<Mon<String>> 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<String> one = Mon.of("test");
final Mon<String> same = Mon.of("test");
final Mon<String> other = Mon.of("other");
//then
assertThat(one).isEqualTo(same);
assertThat(one).isNotEqualTo(other);
}
@Test
public void canFlatMap() {
//given
final Mon<String> wrap = Mon.of("test");
//when
final Mon<Mon<String>> nonFlatMapped = wrap.map(Mon::of);
final Mon<String> 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<Object, Optional<?>> 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<String> one = Mon.of("one");
final Mon<String> two = Mon.of("two");
final Mon<String> 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);
}
}