diff --git a/README.org b/README.org index 694f6d1..8117b51 100644 --- a/README.org +++ b/README.org @@ -119,6 +119,125 @@ } #+END_SRC +*** =TypeAlias= *can* be a Monad + + #+BEGIN_SRC java + package net.kemitix.mon; + + import org.assertj.core.api.WithAssertions; + import org.junit.Test; + + import java.util.function.Function; + + public class TypeAliasMonadTest implements WithAssertions { + + private final int v = 1; + private final Function> f = i -> a(i * 2); + private final Function> g = i -> a(i + 6); + + private static AnAlias a(Integer v) { + return AnAlias.of(v); + } + + @Test + public void leftIdentity() { + assertThat( + a(v).flatMap(f) + ).isEqualTo( + f.apply(v) + ); + } + + @Test + public void rightIdentity_inline() { + // java isn't able to properly infer the correct types when used in-line + assertThat( + a(v).>flatMap(x -> a(x)) + ).isEqualTo( + a(v) + ); + } + + @Test + public void rightIdentity_explicitValue() { + final AnAlias integerAnAlias = a(v).flatMap(x -> a(x)); + assertThat( + integerAnAlias + ).isEqualTo( + a(v) + ); + } + + @Test + public void associativity() { + assertThat( + a(v).flatMap(f).flatMap(g) + ).isEqualTo( + a(v).flatMap(x -> f.apply(x).flatMap(g)) + ); + } + + static class AnAlias extends TypeAlias { + private AnAlias(T value) { + super(value); + } + + static AnAlias of(T value) { + return new AnAlias<>(value); + } + } + } + #+END_SRC + + +*** Instance Methods + +**** =final R map(final Function f)= + + Map the TypeAlias into another value. + + #+BEGIN_SRC java + final StudentId studentId = StudentId.of(123); + final String idString = studentId.map(id -> String.valueOf(id)); + + class StudentId extends TypeAlias { + private StudentId(Integer value) { + super(value); + } + static StudentId of(Integer id) { + return new StudentId(id); + } + } + #+END_SRC + + +**** =final > U flatMap(final Function f)= + + Map the TypeAlias into another TypeAlias. + + #+BEGIN_SRC java + final StudentId studentId = StudentId.of(123); + final StudentName studentName = studentId.flatMap(id -> getStudentName(id)); + + class StudentName extends TypeAlias { + private StudentName(String value) { + super(value); + } + static StudentName of(final String name) { + return new StudentName(name); + } + } + #+END_SRC + + +**** =T getValue()= + + Get the value of the TypeAlias. + + #+BEGIN_SRC java + final String name = studentName.getValue(); + #+END_SRC + ** Maybe @@ -734,7 +853,7 @@ convention, used to indicate the error, and right the success. An alternative is to use the =Result= which more clearly distinguishes success from failure. - =Either= is not a Monad. +*** =Either= *is not* a Monad. *** Static Constructors @@ -746,6 +865,7 @@ final Either left = Either.left(getIntegerValue()); #+END_SRC + **** =static Either right(final R r)= Create a new Either holding a right value. @@ -766,6 +886,7 @@ final boolean rightIsLeft = Either.right(getStringValue()).isLeft(); #+END_SRC + **** =boolean isRight()= Checks if the Either holds a right value. @@ -788,6 +909,7 @@ ); #+END_SRC + **** = Either mapLeft(Function f)= Map the function across the left value. @@ -797,6 +919,7 @@ .mapLeft(i -> i.doubleValue()); #+END_SRC + **** = Either mapRight(Function f)= Map the function across the right value. @@ -805,3 +928,5 @@ final Either either = Either.left(getIntegerValue()) .mapRight(s -> s + "x"); #+END_SRC + + diff --git a/src/main/java/net/kemitix/mon/TypeAlias.java b/src/main/java/net/kemitix/mon/TypeAlias.java index 5d49978..d4df4ef 100644 --- a/src/main/java/net/kemitix/mon/TypeAlias.java +++ b/src/main/java/net/kemitix/mon/TypeAlias.java @@ -30,7 +30,6 @@ import java.util.function.Function; * for the type being aliased.

* * @param the type of the alias - * * @author Paul Campbell (pcampbell@kemitix.net) */ @SuppressWarnings("abstractclassname") @@ -55,13 +54,24 @@ public abstract class TypeAlias { * * @param f the function to create the new value * @param the type of the new value - * - * @return a TypeAlias + * @return the result of the function */ public final R map(final Function f) { return f.apply(value); } + /** + * Map the TypeAlias into another TypeAlias. + * + * @param f the function to create the new value + * @param the type of the new value within a TypeAlias + * @param the type of the TypeAlias superclass containing the new value + * @return a TypeAlias + */ + public final > U flatMap(final Function f) { + return f.apply(value); + } + @Override public final int hashCode() { return value.hashCode(); @@ -73,7 +83,7 @@ public abstract class TypeAlias { final TypeAlias other = (TypeAlias) o; final Class otherValueClass = other.value.getClass(); return otherValueClass.equals(getValue().getClass()) - && other.value.equals(getValue()); + && other.value.equals(getValue()); } return map(o::equals); } diff --git a/src/test/java/net/kemitix/mon/TypeAliasMonadTest.java b/src/test/java/net/kemitix/mon/TypeAliasMonadTest.java new file mode 100644 index 0000000..90c87df --- /dev/null +++ b/src/test/java/net/kemitix/mon/TypeAliasMonadTest.java @@ -0,0 +1,55 @@ +package net.kemitix.mon; + +import org.assertj.core.api.WithAssertions; +import org.junit.Test; + +import java.util.function.Function; + +public class TypeAliasMonadTest implements WithAssertions { + + private final int v = 1; + private final Function> f = i -> a(i * 2); + private final Function> g = i -> a(i + 6); + + private static AnAlias a(Integer v) { + return AnAlias.of(v); + } + + @Test + public void leftIdentity() { + assertThat( + a(v).flatMap(f) + ).isEqualTo( + f.apply(v) + ); + } + + @Test + public void rightIdentity() { + final AnAlias integerAnAlias = a(v).flatMap(x -> a(x)); + assertThat( + integerAnAlias + ).isEqualTo( + a(v) + ); + } + + @Test + public void associativity() { + assertThat( + a(v).flatMap(f).flatMap(g) + ).isEqualTo( + a(v).flatMap(x -> f.apply(x).flatMap(g)) + ); + } + + static class AnAlias extends TypeAlias { + private AnAlias(T value) { + super(value); + } + + static AnAlias of(T value) { + return new AnAlias<>(value); + } + } +}