Skip to content

Instantly share code, notes, and snippets.

@Garciat
Last active June 4, 2020 04:50
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save Garciat/5ec68e4989e46e519dcc79662aba7b70 to your computer and use it in GitHub Desktop.
package com.uber.playground;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.Value;
import org.jetbrains.annotations.Nullable;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Instant;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
@Value
class Unit {}
@FunctionalInterface
interface NullableFunction<T, R> {
@Nullable R apply(T t);
}
abstract class Either<T, F> {
private Either() {}
public abstract <R> R match(Function<T, R> success, Function<F, R> failure);
// semantic
public <E extends Throwable> T orElseThrow(Function<F, E> handler) throws E {
return match(Function.identity(), new Function<F, T>() {
@SneakyThrows
@Override
public T apply(F f) {
throw handler.apply(f);
}
});
}
// structural
// Functor
public <U> Either<U, F> map(Function<T, U> mapper) {
return match(t -> success(mapper.apply(t)), Either::failure);
}
public <G> Either<T, G> mapFailure(Function<F, G> mapper) {
return match(Either::success, f -> failure(mapper.apply(f)));
}
// Monad
public <U> Either<U, F> flatMap(Function<T, Either<U, F>> callback) {
return match(callback, Either::failure);
}
// constructors
public static <T, F> Either<T, F> success(T t) {
Objects.requireNonNull(t);
return new Success<>(t);
}
public static <T, F> Either<T, F> failure(F f) {
Objects.requireNonNull(f);
return new Failure<>(f);
}
public static <T> Either<T, Unit> fromOptional(Optional<T> opt) {
if (opt.isPresent()) {
return success(opt.get());
} else {
return failure(new Unit());
}
}
public static <T> Either<T, Throwable> run(Supplier<T> supplier) {
Objects.requireNonNull(supplier);
T value;
try {
value = supplier.get();
} catch (Throwable t) {
return failure(t);
}
return success(value);
}
public static <A, B> Function<A, Either<B, Throwable>> lift(Function<A, B> func) {
return a -> run(() -> func.apply(a));
}
public static <A, B> Function<A, Either<Optional<B>, Throwable>> liftNullable(NullableFunction<A, B> func) {
return a -> run(() -> Optional.ofNullable(func.apply(a)));
}
// combinators
public static <F, A, B, C> Either<C, F> merge(Either<A, F> eitherA, Either<B, F> eitherB, BiFunction<A, B, C> valueMerge, BinaryOperator<F> failureMerge) {
return eitherA.match(
a -> eitherB.match(b -> Either.success(valueMerge.apply(a, b)), Either::failure),
f1 -> eitherB.match(b -> Either.failure(f1), f2 -> Either.failure(failureMerge.apply(f1, f2))));
}
// data
@Value
private static class Success<T, F> extends Either<T, F> {
@NonNull T t;
@Override
public <R> R match(Function<T, R> success, Function<F, R> failure) {
return success.apply(t);
}
@Override
public String toString() {
return "Either.success(" + t + ")";
}
}
@Value
private static class Failure<T, F> extends Either<T, F> {
@NonNull F f;
@Override
public <R> R match(Function<T, R> success, Function<F, R> failure) {
return failure.apply(f);
}
@Override
public String toString() {
return "Either.failure(" + f + ")";
}
}
}
abstract class ParseFailure {
private ParseFailure() {}
// constructors
public static ParseFailure message(String message) {
return new Message(message);
}
public static ParseFailure exception(Throwable t) {
return new Exception(t);
}
// combinators
public static ParseFailure tag(String tag, ParseFailure sub) {
return new Tag(tag, sub);
}
public static ParseFailure merge(ParseFailure left, ParseFailure right) {
return new Merge(left, right);
}
// data
@Value
public static class Message extends ParseFailure {
String message;
@Override
public String toString() {
return "ParseFailure.message(\"" + message + "\")";
}
}
@Value
public static class Exception extends ParseFailure {
Throwable throwable;
@Override
public String toString() {
String s = "ParseFailure.exception(type=" + throwable.getClass().getName();
if (throwable.getMessage() != null) {
s += ", message=\"" + throwable.getMessage() + '"';
}
return s + ")";
}
}
@Value
public static class Tag extends ParseFailure {
String tag;
ParseFailure sub;
@Override
public String toString() {
return "ParseFailure.tag(\"" + tag + "\", " + sub + ")";
}
}
@Value
public static class Merge extends ParseFailure {
ParseFailure left, right;
@Override
public String toString() {
return "ParseFailure.merge(" + left + ", " + right + ")";
}
}
}
class ParseException extends RuntimeException {
public ParseException(ParseFailure failure) {
super(failure.toString());
}
}
interface Parser<T, R> {
Either<R, ParseFailure> parse(T input);
default R parseStrict(T input) throws ParseException {
return parse(input).orElseThrow(ParseException::new);
}
// semantic
default Parser<T, R> tagged(String tag) {
return input -> parse(input).mapFailure(f -> ParseFailure.tag(tag, f));
}
// structural
// Functor
default <S> Parser<T, S> map(Function<R, S> mapper) {
return andThen(lift(mapper));
}
// Category (composition)
default <S> Parser<T, S> andThen(Parser<R, S> next) {
return input -> parse(input).flatMap(next::parse);
}
// Alternative
default Parser<T, R> recoverWith(Parser<T, R> fallback) {
return input -> parse(input)
.match(Either::success, $ -> fallback.parse(input));
}
// constructors
static <T> Parser<T, T> id() {
return input -> Either.success(input);
}
// Pure
static <T, R> Parser<T, R> returning(R r) {
return input -> Either.success(r);
}
static <T, R> Parser<T, R> fail() {
return input -> Either.failure(ParseFailure.message("fail"));
}
static <T> Parser<Optional<T>, T> nonEmpty() {
return input -> Either.fromOptional(input)
.mapFailure(unit -> ParseFailure.message("value is null"));
}
static <T, R> Parser<T, R> lift(Function<T, R> function) {
return input -> Either.lift(function).apply(input)
.mapFailure(ParseFailure::exception);
}
static <T, R> Parser<T, Optional<R>> liftNullable(NullableFunction<T, R> function) {
return input -> Either.liftNullable(function).apply(input)
.mapFailure(ParseFailure::exception);
}
static <T, R> Parser<T, R> liftPure(Function<T, R> function) {
return input -> Either.success(function.apply(input));
}
static <T, R> Parser<Optional<T>, Optional<R>> liftOptional(Parser<T, R> parser) {
return input -> input.isPresent()
? parser.parse(input.get()).map(Optional::of)
: Either.success(Optional.empty());
}
// combinators
// Applicative
static <T, A, B, C> Parser<T, C> merge(Parser<T, A> left, Parser<T, B> right, BiFunction<A, B, C> merger) {
return input -> Either.merge(left.parse(input), right.parse(input), merger, ParseFailure::merge);
}
}
abstract class ParserComposition<T, R, F> {
public final Parser<T, R> build(F f) {
return build()
.map(cont -> cont.apply(f))
.tagged(getInputType().getSimpleName());
}
protected abstract Parser<T, Function<F, R>> build();
protected abstract Class<T> getInputType();
protected abstract Class<R> getResultType();
public <A, B> ParserComposition<T, R, Function<B, F>> required(Getter<T, A> getter, Parser<A, B> parser) {
return with(Parser.liftNullable(getter)
.andThen(Parser.nonEmpty())
.andThen(parser)
.tagged(getter.getInfo().getPropertyName()));
}
public <A> ParserComposition<T, R, Function<A, F>> required(Getter<T, A> getter) {
return required(getter, Parser.id());
}
public <A, B> ParserComposition<T, R, Function<Optional<B>, F>> optional(Getter<T, A> getter, Parser<A, B> parser) {
return with(Parser.liftNullable(getter)
.andThen(Parser.liftOptional(parser))
.tagged(getter.getInfo().getPropertyName()));
}
public <A> ParserComposition<T, R, Function<Optional<A>, F>> optional(Getter<T, A> getter) {
return optional(getter, Parser.id());
}
public <A> ParserComposition<T, R, Function<A, F>> with(Parser<T, A> field) {
return new ParserComposition<T, R, Function<A, F>>() {
@Override
protected Parser<T, Function<Function<A, F>, R>> build() {
Parser<T, Function<F, R>> parent = ParserComposition.this.build();
return Parser.merge(field, parent, (a, fr) -> af -> fr.apply(af.apply(a)));
}
@Override
protected Class<T> getInputType() {
return ParserComposition.this.getInputType();
}
@Override
protected Class<R> getResultType() {
return ParserComposition.this.getResultType();
}
};
}
public static <T, R> ParserComposition<T, R, R> of(Class<T> input, Class<R> result) {
return new ParserComposition<T, R, R>() {
@Override
protected Parser<T, Function<R, R>> build() {
return Parser.returning(r -> r);
}
@Override
protected Class<T> getInputType() {
return input;
}
@Override
protected Class<R> getResultType() {
return result;
}
};
}
}
// https://stackoverflow.com/a/21879031
@FunctionalInterface
interface Getter<T, R> extends NullableFunction<T, R>, Serializable {
default Info getInfo() {
for (Class<?> cl = getClass(); cl != null; cl = cl.getSuperclass()) {
try {
Method m = cl.getDeclaredMethod("writeReplace");
m.setAccessible(true);
Object replacement = m.invoke(this);
if(!(replacement instanceof SerializedLambda))
break;// custom interface implementation
SerializedLambda l = (SerializedLambda) replacement;
return new Info(l.getImplClass(), l.getImplMethodName());
}
catch (NoSuchMethodException e) {}
catch (IllegalAccessException | InvocationTargetException e) {
break;
}
}
throw new RuntimeException("not a method reference");
}
@Value
class Info {
String implClass;
String implMethodName;
String toReferenceName() {
return implClass + "::" + implMethodName;
}
String getPropertyName() {
if (implMethodName.startsWith("get")) {
return implMethodName.substring("get".length());
} else {
return implMethodName;
}
}
}
}
@Value
class ThriftThing {
@Nullable String uuid;
@Nullable String timestamp;
@Nullable String currencyCode;
@Nullable ThriftNested nested;
}
@Value
class ThriftNested {
@Nullable String name;
}
@Value
class ProperThing {
UUID uuid;
Instant timestamp;
Optional<Currency> currency;
ProperNested nested;
}
@Value
class ProperNested {
String name;
}
public class Program {
public static void main(String[] args) {
ThriftThing bad1 = new ThriftThing("123", null, "111", null);
ThriftThing bad2 = new ThriftThing("123", null, "111", new ThriftNested(null));
ThriftThing good = new ThriftThing("1-1-1-1-1", "2020-05-20T10:23:31Z", null, new ThriftNested("hello"));
Parser<String, UUID> uuidParser = Parser.lift(UUID::fromString);
Parser<String, Instant> instantParser = Parser.lift(Instant::parse);
Parser<String, Currency> currencyParser = Parser.lift(Currency::getInstance);
Parser<ThriftNested, ProperNested> nestedParser =
ParserComposition.of(ThriftNested.class, ProperNested.class)
.required(ThriftNested::getName)
.build(reversed(curry(ProperNested::new)));
Parser<ThriftThing, ProperThing> thingParser =
ParserComposition.of(ThriftThing.class, ProperThing.class)
.required(ThriftThing::getUuid, uuidParser)
.required(ThriftThing::getTimestamp, instantParser)
.optional(ThriftThing::getCurrencyCode, currencyParser)
.required(ThriftThing::getNested, nestedParser)
.build(reversed(curry(ProperThing::new)));
System.out.println(thingParser.parse(bad1));
System.out.println(thingParser.parse(bad2));
System.out.println(thingParser.parse(good));
/*
Either.failure(ParseFailure.tag("ThriftThing", ParseFailure.merge(ParseFailure.tag("Nested", ParseFailure.message("value is null")), ParseFailure.merge(ParseFailure.tag("CurrencyCode", ParseFailure.exception(type=java.lang.IllegalArgumentException)), ParseFailure.merge(ParseFailure.tag("Timestamp", ParseFailure.message("value is null")), ParseFailure.tag("Uuid", ParseFailure.exception(type=java.lang.IllegalArgumentException, message="Invalid UUID string: 123")))))))
Either.failure(ParseFailure.tag("ThriftThing", ParseFailure.merge(ParseFailure.tag("Nested", ParseFailure.tag("ThriftNested", ParseFailure.tag("Name", ParseFailure.message("value is null")))), ParseFailure.merge(ParseFailure.tag("CurrencyCode", ParseFailure.exception(type=java.lang.IllegalArgumentException)), ParseFailure.merge(ParseFailure.tag("Timestamp", ParseFailure.message("value is null")), ParseFailure.tag("Uuid", ParseFailure.exception(type=java.lang.IllegalArgumentException, message="Invalid UUID string: 123")))))))
Either.success(ProperThing(uuid=00000001-0001-0001-0001-000000000001, timestamp=2020-05-20T10:23:31Z, currency=Optional.empty, nested=ProperNested(name=hello)))
*/
}
static <A, R> Fn1<A, R> curry(Fn1<A, R> f) {
return f;
}
static <A, B, R> Fn2<A, B, R> curry(Fn2<A, B, R> f) {
return f;
}
static <A, B, C, R> Fn3<A, B, C, R> curry(Fn3<A, B, C, R> f) {
return f;
}
static <A, B, C, D, R> Fn4<A, B, C, D, R> curry(Fn4<A, B, C, D, R> f) {
return f;
}
static <A, R> Fn1<A, R> reversed(Fn1<A, R> f) {
return f.rev1();
}
static <A, B, R> Fn2<B, A, R> reversed(Fn2<A, B, R> f) {
return f.rev2();
}
static <A, B, C, R> Fn3<C, B, A, R> reversed(Fn3<A, B, C, R> f) {
return f.rev3();
}
static <A, B, C, D, R> Fn4<D, C, B, A, R> reversed(Fn4<A, B, C, D, R> f) {
return f.rev4();
}
}
@FunctionalInterface
interface Fn1<A, R> extends Function<A, R> {
@Override
R apply(A a);
default Fn1<A, R> rev1() {
return this;
}
}
@FunctionalInterface
interface Fn2<A, B, R> extends Fn1<A, Function<B, R>> {
R apply(A a, B b);
@Override
default Function<B, R> apply(A a) {
return b -> apply(a, b);
}
default Fn2<B, A, R> rev2() {
return (b, a) -> apply(a, b);
}
}
@FunctionalInterface
interface Fn3<A, B, C, R> extends Fn2<A, B, Function<C, R>> {
R apply(A a, B b, C c);
@Override
default Function<C, R> apply(A a, B b) {
return c -> apply(a, b, c);
}
default Fn3<C, B, A, R> rev3() {
return (c, b, a) -> apply(a, b, c);
}
}
@FunctionalInterface
interface Fn4<A, B, C, D, R> extends Fn3<A, B, C, Function<D, R>> {
R apply(A a, B b, C c, D d);
@Override
default Function<D, R> apply(A a, B b, C c) {
return d -> apply(a, b, c, d);
}
default Fn4<D, C, B, A, R> rev4() {
return (d, c, b, a) -> apply(a, b, c, d);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment