Last active
October 8, 2018 21:32
-
-
Save SegFaultAX/3d60d88b7620e7bd9fc59bc451be7be7 to your computer and use it in GitHub Desktop.
Functional composable applicative streaming monoidal folds v2 [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.Collection; | |
import java.util.List; | |
import java.util.function.Function; | |
public interface Fold<Acc, From, To> { | |
Monoid<Acc> ev(); | |
Acc quantify(From x); | |
To combine(Acc acc); | |
default To fold(Collection<From> xs) { | |
return Folds.fold(this, xs); | |
} | |
default List<To> scan(Collection<From> xs) { | |
return Folds.scan(this, xs); | |
} | |
default <R> Fold<Acc, From, R> map(Function<To, R> fn) { | |
return Folds.map(this, fn); | |
} | |
} |
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.List; | |
import java.util.function.BiFunction; | |
import java.util.function.Function; | |
import java.util.stream.Collector; | |
import com.google.common.collect.ImmutableList; | |
public final class Folds { | |
private Folds() {} | |
public static <A, F, T> Fold<A, F, T> using(Monoid<A> m, Function<F, A> q, Function<A, T> c) { | |
return new Fold<>() { | |
@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); | |
} | |
}; | |
} | |
public static <A, F> Fold<A, F, A> using(Monoid<A> m, Function<F, A> q) { | |
return using(m, q, Function.identity()); | |
} | |
public static <A> Fold<A, A, A> using(Monoid<A> m) { | |
return using(m, Function.identity(), Function.identity()); | |
} | |
public static <A, F, T> T fold(Fold<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); | |
} | |
public static <A, F, T> List<T> scan(Fold<A, F, T> f, Collection<F> xs) { | |
ImmutableList.Builder<T> builder = ImmutableList.builder(); | |
A acc = f.ev().empty(); | |
builder.add(f.combine(acc)); | |
for (F x : xs) { | |
acc = f.ev().append(acc, f.quantify(x)); | |
builder.add(f.combine(acc)); | |
} | |
return builder.build(); | |
} | |
public static <A, F, T1, T2> Fold<A, F, T2> map(Fold<A, F, T1> f, Function<T1, T2> fn) { | |
return new Fold<>() { | |
@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)); | |
} | |
}; | |
} | |
public static <F, T> Fold<Void, F, T> pure(T v) { | |
return new Fold<>() { | |
@Override | |
public Monoid<Void> ev() { | |
return Monoids.of(() -> null, (a, b) -> null); | |
} | |
@Override | |
public Void quantify(F x) { | |
return null; | |
} | |
@Override | |
public T combine(Void aVoid) { | |
return v; | |
} | |
}; | |
} | |
public static <A1, A2, F, T1, T2> Fold<Pair<A1, A2>, F, T2> apply(Fold<A1, F, Function<T1, T2>> f, Fold<A2, F, T1> v) { | |
Monoid<Pair<A1, A2>> pairEv = Monoids.pairs(f.ev(), v.ev()); | |
return new Fold<>() { | |
@Override | |
public Monoid<Pair<A1, A2>> ev() { | |
return pairEv; | |
} | |
@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())); | |
} | |
}; | |
} | |
public static <A1, A2, T1, T2, F, R> Fold<Pair<A1, A2>, F, R> liftA2( | |
BiFunction<T1, T2, R> fn, | |
Fold<A1, F, T1> f1, | |
Fold<A2, F, T2> f2) { | |
Function<T1, Function<T2, R>> curried = a -> b -> fn.apply(a, b); | |
return apply(f1.map(curried), f2); | |
} | |
public static <A1, A2, T1, T2, F, R> Fold<Pair<A1, A2>, F, R> liftA2( | |
Function<T1, Function<T2, R>> fn, | |
Fold<A1, F, T1> f1, | |
Fold<A2, F, T2> f2) { | |
return apply(f1.map(fn), f2); | |
} | |
public static <A, F, T> Collector<F, MutableBox<A>, T> toCollector(Fold<A, F, T> f) { | |
return Collector.of( | |
() -> MutableBox.of(f.ev().empty()), // supplier | |
(m, x) -> m.update(v -> f.ev().append(v, f.quantify(x))), // accumulator | |
(a, b) -> MutableBox.of(f.ev().append(a.getValue(), b.getValue())), // combiner | |
m -> f.combine(m.getValue()) // finisher | |
); | |
} | |
} |
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 static org.assertj.core.api.Assertions.assertThat; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.stream.Stream; | |
import org.junit.Test; | |
public class FoldTest { | |
private final Monoid<Integer> monoidSum = Monoids.of(() -> 0, (a, b) -> a + b); | |
private final Monoid<Integer> monoidProduct = Monoids.of(() -> 1, (a, b) -> a * b); | |
private final Fold<Integer, Integer, Integer> sumFold = Folds.using(monoidSum); | |
private final Fold<Integer, Integer, Integer> productFold = Folds.using(monoidProduct); | |
@Test | |
public void testSimpleFold() { | |
assertThat(sumFold.fold(List.of(1, 2, 3, 4))).isEqualTo(10); | |
} | |
@Test | |
public void testFoldScan() { | |
assertThat(sumFold.scan(List.of(1, 2, 3, 4))).containsExactly(0, 1, 3, 6, 10); | |
} | |
@Test | |
public void testFoldFunctor() { | |
assertThat(sumFold.map(v -> Integer.toString(v)).fold(List.of(1, 2, 3, 4))).isEqualTo("10"); | |
} | |
@Test | |
public void testFoldApplicativePure() { | |
assertThat(Folds.pure(10).fold(List.of(1, 2))).isEqualTo(10); | |
} | |
@Test | |
public void testFoldApplicativeApply() { | |
Fold<Pair<Pair<Void, Integer>, Integer>, Integer, Pair<Integer, Integer>> tuplingSumProduct = | |
Folds.apply(Folds.apply(Folds.pure(Pair.curried()), sumFold), productFold); | |
assertThat(tuplingSumProduct.fold(List.of(1, 2, 3, 4))).isEqualTo(Pair.of(10, 24)); | |
} | |
@Test | |
public void testFoldApplicativeLift() { | |
Fold<Pair<Integer, Integer>, Integer, Pair<Integer, Integer>> tuplingSumProduct = | |
Folds.liftA2(Pair::of, sumFold, productFold); | |
assertThat(tuplingSumProduct.fold(List.of(1, 2, 3, 4))).isEqualTo(Pair.of(10, 24)); | |
} | |
@Test | |
public void testFoldCustomQuantify() { | |
Fold<Integer, Object, Integer> lenFold = Folds.using(monoidSum, v -> 1); | |
assertThat(lenFold.fold(List.of(1, 2, 3, 4))).isEqualTo(4); | |
} | |
@Test | |
public void testFoldMapping() { | |
Monoid<Map<String, Integer>> countByKey = Monoids.mapping(monoidSum); | |
Fold<Map<String, Integer>, Integer, Map<String, Integer>> foldCountByKey = | |
Folds.using(countByKey, v -> Map.of(v % 2 == 0 ? "even" : "odd", 1)); | |
assertThat(foldCountByKey.fold(List.of(1, 2, 3, 4, 5, 6, 7))) | |
.containsEntry("even", 3) | |
.containsEntry("odd", 4); | |
} | |
@Test | |
public void testFoldCollector() { | |
Fold<Pair<Integer, Integer>, Integer, Pair<Integer, Integer>> tuplingSumProduct = | |
Folds.liftA2(Pair::of, sumFold, productFold); | |
assertThat(Stream.of(1, 2, 3, 4).collect(tuplingSumProduct.toCollector())) | |
.isEqualTo(Pair.of(10, 24)); | |
} | |
} |
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 Monoid<T> extends Semigroup<T> { | |
T empty(); | |
} |
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.HashMap; | |
import java.util.Map; | |
import java.util.function.BiFunction; | |
import java.util.function.Supplier; | |
import com.google.common.collect.ImmutableMap; | |
public final class Monoids { | |
private Monoids() {} | |
public 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); | |
} | |
}; | |
} | |
public static <K, T> Monoid<Map<K, T>> mapping(Monoid<T> ev) { | |
return new Monoid<>() { | |
@Override | |
public Map<K, T> empty() { | |
return ImmutableMap.of(); | |
} | |
@Override | |
public Map<K, T> append(Map<K, T> a, Map<K, T> b) { | |
Map<K, T> acc = new HashMap<>(a); | |
b.forEach((k, v) -> acc.merge(k ,v, ev::append)); | |
return ImmutableMap.copyOf(acc); | |
} | |
}; | |
} | |
public static <A, B> Monoid<Pair<A, B>> pairs(Monoid<A> evA, Monoid<B> evB) { | |
return new Monoid<>() { | |
@Override | |
public Pair<A, B> empty() { | |
return Pair.of(evA.empty(), evB.empty()); | |
} | |
@Override | |
public Pair<A, B> append(Pair<A, B> a, Pair<A, B> b) { | |
return Pair.of(evA.append(a.left(), b.left()), evB.append(a.right(), b.right())); | |
} | |
}; | |
} | |
} |
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.Function; | |
public class MutableBox<T> { | |
private T value; | |
private MutableBox(T value) { | |
this.value = value; | |
} | |
public static <T> MutableBox<T> of(T value) { | |
return new MutableBox<>(value); | |
} | |
public T getValue() { | |
return value; | |
} | |
public MutableBox<T> update(Function<T, T> fn) { | |
value = fn.apply(value); | |
return this; | |
} | |
} |
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
@FunctionalInterface | |
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