Last active
October 6, 2018 18:23
-
-
Save SegFaultAX/924dffca5d484a25871d8317391cc51f to your computer and use it in GitHub Desktop.
Functional composable applicative streaming monoidal folds [Java]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.function.BiFunction; | |
import java.util.function.Supplier; | |
public interface Monoid<T> extends Semigroup<T> { | |
T empty(); | |
static <T> Monoid<T> of(Supplier<T> v, BiFunction<T, T, T> app) { | |
return new Monoid<>() { | |
@Override | |
public T empty() { | |
return v.get(); | |
} | |
@Override | |
public T append(T a, T b) { | |
return app.apply(a, b); | |
} | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Collection; | |
import java.util.function.BiFunction; | |
import java.util.function.Function; | |
import java.util.function.Supplier; | |
public interface MonoidFold<Acc, From, To> { | |
Monoid<Acc> ev(); | |
Acc quantify(From x); | |
To combine(Acc acc); | |
static <A, F, T> MonoidFold<A, F, T> using(Monoid<A> m, Function<F, A> q, Function<A, T> c) { | |
return new MonoidFold<>() { | |
@Override | |
public Monoid<A> ev() { | |
return m; | |
} | |
@Override | |
public A quantify(F x) { | |
return q.apply(x); | |
} | |
@Override | |
public T combine(A a) { | |
return c.apply(a); | |
} | |
}; | |
} | |
static <A, F> MonoidFold<A, F, A> using(Monoid<A> m, Function<F, A> q) { | |
return using(m, q, Function.identity()); | |
} | |
static <A> MonoidFold<A, A, A> using(Monoid<A> m) { | |
return using(m, Function.identity(), Function.identity()); | |
} | |
static <A, F, T> T fold(MonoidFold<A, F, T> f, Collection<F> xs) { | |
A acc = f.ev().empty(); | |
for (F x : xs) { | |
acc = f.ev().append(acc, f.quantify(x)); | |
} | |
return f.combine(acc); | |
} | |
default To fold(Collection<From> xs) { | |
return fold(this, xs); | |
} | |
static <A, F, T1, T2> MonoidFold<A, F, T2> map(MonoidFold<A, F, T1> f, Function<T1, T2> fn) { | |
return new MonoidFold<>() { | |
@Override | |
public Monoid<A> ev() { | |
return f.ev(); | |
} | |
@Override | |
public A quantify(F x) { | |
return f.quantify(x); | |
} | |
@Override | |
public T2 combine(A a) { | |
return fn.apply(f.combine(a)); | |
} | |
}; | |
} | |
default <R> MonoidFold<Acc, From, R> map(Function<To, R> fn) { | |
return MonoidFold.map(this, fn); | |
} | |
static <F, T> MonoidFold<Void, F, T> pure(T v) { | |
return new MonoidFold<>() { | |
@Override | |
public Monoid<Void> ev() { | |
return Monoid.of(() -> null, (a, b) -> null); | |
} | |
@Override | |
public Void quantify(F x) { | |
return null; | |
} | |
@Override | |
public T combine(Void aVoid) { | |
return v; | |
} | |
}; | |
} | |
static <A1, A2, F, T1, T2> MonoidFold<Pair<A1, A2>, F, T2> apply(MonoidFold<A1, F, Function<T1, T2>> f, MonoidFold<A2, F, T1> v) { | |
return new MonoidFold<>() { | |
@Override | |
public Monoid<Pair<A1, A2>> ev() { | |
return new Monoid<>() { | |
@Override | |
public Pair<A1, A2> empty() { | |
return Pair.of(f.ev().empty(), v.ev().empty()); | |
} | |
@Override | |
public Pair<A1, A2> append(Pair<A1, A2> a, Pair<A1, A2> b) { | |
return Pair.of(f.ev().append(a.left(), b.left()), v.ev().append(a.right(), b.right())); | |
} | |
}; | |
} | |
@Override | |
public Pair<A1, A2> quantify(F x) { | |
return Pair.of(f.quantify(x), v.quantify(x)); | |
} | |
@Override | |
public T2 combine(Pair<A1, A2> acc) { | |
return f.combine(acc.left()).apply(v.combine(acc.right())); | |
} | |
}; | |
} | |
static <A1, A2, T1, T2, F, R> MonoidFold<Pair<A1, A2>, F, R> liftA2( | |
BiFunction<T1, T2, R> fn, | |
MonoidFold<A1, F, T1> f1, | |
MonoidFold<A2, F, T2> f2) { | |
Function<T1, Function<T2, R>> curried = a -> b -> fn.apply(a, b); | |
return MonoidFold.apply(f1.map(curried), f2); | |
} | |
static <A1, A2, T1, T2, F, R> MonoidFold<Pair<A1, A2>, F, R> liftA2( | |
Function<T1, Function<T2, R>> fn, | |
MonoidFold<A1, F, T1> f1, | |
MonoidFold<A2, F, T2> f2) { | |
return MonoidFold.apply(f1.map(fn), f2); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.jupiter.api.Test; | |
import java.util.List; | |
import static org.assertj.core.api.Assertions.assertThat; | |
public class MonoidFoldTest { | |
private final Monoid<Integer> monoidSum = Monoid.of(() -> 0, (a, b) -> a + b); | |
private final Monoid<Integer> monoidProduct = Monoid.of(() -> 1, (a, b) -> a * b); | |
private final MonoidFold<Integer, Integer, Integer> sumFold = MonoidFold.using(monoidSum); | |
private final MonoidFold<Integer, Integer, Integer> productFold = MonoidFold.using(monoidProduct); | |
@Test | |
public void testSimpleMonoidFold() { | |
assertThat(sumFold.fold(List.of(1, 2, 3, 4))).isEqualTo(10); | |
} | |
@Test | |
public void testMonoidFoldFunctor() { | |
assertThat(sumFold.map(v -> Integer.toString(v)).fold(List.of(1, 2, 3, 4))).isEqualTo("10"); | |
} | |
@Test | |
public void testMonoidFoldApplicativePure() { | |
assertThat(Fold.pure(10).fold(List.of(1, 2))).isEqualTo(10); | |
} | |
@Test | |
public void testMonoidFoldApplicativeApply() { | |
MonoidFold<Pair<Pair<Void, Integer>, Integer>, Integer, Pair<Integer, Integer>> tuplingSumProduct = | |
MonoidFold.apply(MonoidFold.apply(MonoidFold.pure(Pair.curried()), sumFold), productFold); | |
assertThat(tuplingSumProduct.fold(List.of(1, 2, 3, 4))).isEqualTo(Pair.of(10, 24)); | |
} | |
@Test | |
public void testMonoidFoldApplicativeLift() { | |
MonoidFold<Pair<Integer, Integer>, Integer, Pair<Integer, Integer>> tuplingSumProduct = | |
MonoidFold.liftA2(Pair::of, sumFold, productFold); | |
assertThat(tuplingSumProduct.fold(List.of(1, 2, 3, 4))).isEqualTo(Pair.of(10, 24)); | |
} | |
@Test | |
public void testMonoidFoldCustomQuantify() { | |
MonoidFold<Integer, Object, Integer> lenFold = MonoidFold.using(monoidSum, v -> 1); | |
assertThat(lenFold.fold(List.of(1, 2, 3, 4))).isEqualTo(4); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Objects; | |
import java.util.function.Function; | |
public class Pair<A, B> { | |
private final A a; | |
private final B b; | |
public Pair(A a, B b) { | |
this.a = a; | |
this.b = b; | |
} | |
public static <A, B> Pair<A, B> of(A a, B b) { | |
return new Pair<>(a, b); | |
} | |
public static <A, B> Function<A, Function<B, Pair<A, B>>> curried() { | |
return a -> b -> of(a, b); | |
} | |
public A left() { | |
return a; | |
} | |
public B right() { | |
return b; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Pair<?, ?> pair = (Pair<?, ?>) o; | |
return Objects.equals(a, pair.a) && | |
Objects.equals(b, pair.b); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(a, b); | |
} | |
@Override | |
public String toString() { | |
return "Pair{" + | |
"a=" + a + | |
", b=" + b + | |
'}'; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface Semigroup<T> { | |
T append(T a, T b); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment