> mapper);
+
/**
*
* Returns the content of Right or a default value
diff --git a/src/main/java/com/ceticamarco/lambdatonic/Left.java b/src/main/java/com/ceticamarco/lambdatonic/Left.java
index d74faac..e7bf6c8 100644
--- a/src/main/java/com/ceticamarco/lambdatonic/Left.java
+++ b/src/main/java/com/ceticamarco/lambdatonic/Left.java
@@ -33,6 +33,11 @@ public record Left(L value) implements Either {
return new Left<>(onLeft.apply(this.value));
}
+ @Override
+ public Either flatMap(Function> mapper) {
+ return new Left<>(this.value);
+ }
+
@Override
public R fromRight(R defaultValue) {
return defaultValue;
diff --git a/src/main/java/com/ceticamarco/lambdatonic/Right.java b/src/main/java/com/ceticamarco/lambdatonic/Right.java
index fe07414..86a554b 100644
--- a/src/main/java/com/ceticamarco/lambdatonic/Right.java
+++ b/src/main/java/com/ceticamarco/lambdatonic/Right.java
@@ -33,6 +33,11 @@ public record Right(R value) implements Either {
return new Right<>(onRight.apply(this.value));
}
+ @Override
+ public Either flatMap(Function> mapper) {
+ return mapper.apply(this.value);
+ }
+
@Override
public R fromRight(R defaultValue) {
return this.value;
diff --git a/src/test/java/com/ceticamarco/lambdatonic/LeftTests.java b/src/test/java/com/ceticamarco/lambdatonic/LeftTests.java
index 8eeeefe..98b4664 100644
--- a/src/test/java/com/ceticamarco/lambdatonic/LeftTests.java
+++ b/src/test/java/com/ceticamarco/lambdatonic/LeftTests.java
@@ -52,6 +52,20 @@ public class LeftTests {
}
}
+ @Test
+ public void testMonadMapLeft() {
+ Function>> division = x -> y ->
+ y == 0 ? new Left<>(new Error("Cannot divide by zero")) : new Right<>(x / y);
+
+ Either res = new Right(10)
+ .flatMap(x -> division.apply(x).apply(0));
+
+ switch (res) {
+ case Left left -> assertEquals(left.value().getMessage(), "Cannot divide by zero");
+ case Right _ -> { }
+ }
+ }
+
@Test
public void testFunctorBiMapLeft() {
Function f = x -> x + 1;
@@ -62,6 +76,7 @@ public class LeftTests {
assertEquals(actual.fromLeft(-1), 20);
}
+ // Functor Laws
@Test
public void testLeftFunctorIdentityMorphism() {
// Applying the map function with the identity function,
@@ -86,6 +101,47 @@ public class LeftTests {
assertEquals(composition, FGMapped);
}
+ // Monad Laws
+ @Test
+ public void testMonadLeftIdentity() {
+ // f :: a -> Either e b
+ Function> f = x -> new Right<>(x + 1);
+
+ // Create a new Left instance
+ Either leftValue = new Left<>(new Error("An error occurred"));
+
+ // leftValue.flatmap(f) == leftValue
+ Either result = leftValue.flatMap(f);
+
+ assertEquals(leftValue, result);
+ }
+
+ @Test
+ public void testMonadRightIdentity() {
+ Either left = new Left<>(new Error("An error occurred"));
+
+ // flatMap(m) == m
+ Either result = left.flatMap(Right::new);
+
+ assertEquals(left, result);
+ }
+
+ @Test
+ public void testMonadAssociativity() {
+ // f :: a -> Either e b
+ // g :: a -> Either e b
+ Function> f = x -> new Right<>(x + 1);
+ Function> g = x -> new Right<>(x * 2);
+
+ // m.flatMap(f).flatmap(g) == m.flatMap(x -> f.apply(x).flatMap(g))
+ Either leftSide = this.numEither.flatMap(f).flatMap(g);
+ Either rightSide = this.numEither.flatMap(x -> f.apply(x).flatMap(g));
+
+ // In both cases, the Left should be left untouched without invoking neither `f` nor `g`
+ assertEquals(this.numEither, leftSide);
+ assertEquals(this.numEither, rightSide);
+ }
+
@Test
public void testFromRightOnLeft() {
assertEquals(this.numEither.fromRight(-1), -1);
diff --git a/src/test/java/com/ceticamarco/lambdatonic/RightTests.java b/src/test/java/com/ceticamarco/lambdatonic/RightTests.java
index 4bab9ff..7d32ec9 100644
--- a/src/test/java/com/ceticamarco/lambdatonic/RightTests.java
+++ b/src/test/java/com/ceticamarco/lambdatonic/RightTests.java
@@ -52,6 +52,20 @@ public class RightTests {
}
}
+ @Test
+ public void testMonadMapRight() {
+ Function>> division = x -> y ->
+ y == 0 ? new Left<>(new Error("Cannot divide by zero")) : new Right<>(x / y);
+
+ Either res = new Right(10)
+ .flatMap(x -> division.apply(x).apply(5));
+
+ switch (res) {
+ case Left _ -> { }
+ case Right right -> assertEquals(right.value(), 2);
+ }
+ }
+
@Test
public void testFunctorBiMapRight() {
Function f = x -> x + 1;
@@ -62,6 +76,7 @@ public class RightTests {
assertEquals(actual.fromRight("-1"), "QUERY EXECUTED SUCCESSFULLY");
}
+ // Functor Laws
@Test
public void testRightFunctorIdentityMorphism() {
// Applying the map function with the identity function,
@@ -86,6 +101,43 @@ public class RightTests {
assertEquals(composition, FGMapped);
}
+ // Monad Laws
+ @Test
+ public void testMonadLeftIdentity() {
+ // f :: a -> Either e b
+ Function> f = x -> new Right<>(x + 1);
+
+ // rightValue.flatMap(f) == f.apply(rightValue)
+ Either actual = new Right(10).flatMap(f);
+ Either expected = f.apply(10);
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testMonadRightIdentity() {
+ Either right = new Right<>(10);
+
+ // flatMap(m) == m
+ Either result = right.flatMap(Right::new);
+
+ assertEquals(right, result);
+ }
+
+ @Test
+ public void testMonadAssociativity() {
+ // f :: a -> Either e b
+ // g :: a -> Either e b
+ Function> f = x -> new Right<>(x + 1);
+ Function> g = x -> new Right<>(x * 2);
+
+ // m.flatMap(f).flatmap(g) == m.flatMap(x -> f.apply(x).flatMap(g))
+ Either leftSide = this.numEither.flatMap(f).flatMap(g);
+ Either rightSide = this.numEither.flatMap(x -> f.apply(x).flatMap(g));
+
+ assertEquals(leftSide, rightSide);
+ }
+
@Test
public void testFromRightOnRight() {
assertEquals(this.numEither.fromRight(-1), 4);