Mon<T> implements Functor<T>
This commit is contained in:
parent
b1066bb4b0
commit
0c178e8821
2 changed files with 289 additions and 0 deletions
142
src/main/java/net/kemitix/mon/Mon.java
Normal file
142
src/main/java/net/kemitix/mon/Mon.java
Normal 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);
|
||||
}
|
||||
}
|
147
src/test/java/net/kemitix/mon/MonTest.java
Normal file
147
src/test/java/net/kemitix/mon/MonTest.java
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue