All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jnape.palatable.lambda.adt.These Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
package com.jnape.palatable.lambda.adt;

import com.jnape.palatable.lambda.adt.coproduct.CoProduct2;
import com.jnape.palatable.lambda.adt.coproduct.CoProduct3;
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
import com.jnape.palatable.lambda.functions.recursion.Trampoline;
import com.jnape.palatable.lambda.functions.specialized.Pure;
import com.jnape.palatable.lambda.functor.Applicative;
import com.jnape.palatable.lambda.functor.Bifunctor;
import com.jnape.palatable.lambda.functor.builtin.Lazy;
import com.jnape.palatable.lambda.monad.Monad;
import com.jnape.palatable.lambda.monad.MonadRec;
import com.jnape.palatable.lambda.traversable.Traversable;

import java.util.Objects;

import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into;
import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence;
import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;

/**
 * The coproduct of a coproduct ({@link CoProduct2}<A, B>) and its product ({@link
 * Tuple2}<A, B>), represented as a {@link CoProduct3}<A, B, {@link Tuple2}<A,
 * B>>.
 *
 * @param  the first possible type
 * @param  the second possible type
 */
public abstract class These implements
        CoProduct3, These>,
        MonadRec>,
        Bifunctor>,
        Traversable> {

    private These() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These biMap(Fn1 lFn,
                                          Fn1 rFn) {
        return match(a -> a(lFn.apply(a)), b -> b(rFn.apply(b)), into((a, b) -> both(lFn.apply(a), rFn.apply(b))));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These flatMap(Fn1>> f) {
        return match(These::a,
                     b -> f.apply(b).coerce(),
                     into((a, b) -> f.apply(b)
                             .>coerce()
                             .match(constantly(a(a)),
                                    c -> both(a, c),
                                    into((__, c) -> both(a, c)))));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  These trampolineM(
            Fn1, These>> fn) {
        return flatMap(Trampoline.>trampoline(
                b -> sequence(fn.apply(b).>>coerce(),
                              RecursiveResult::terminate)));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These pure(C c) {
        return match(a -> both(a, c), b -> b(c), into((a, b) -> both(a, c)));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public , TravC extends Traversable>,
            AppTrav extends Applicative>
    AppTrav traverse(Fn1> fn, Fn1 pure) {
        return match(a -> pure.apply(These.a(a).coerce()),
                     b -> fn.apply(b).fmap(this::pure).fmap(Applicative::coerce).coerce(),
                     into((a, b) -> fn.apply(b).fmap(c -> both(a, c)).fmap(Applicative::coerce).coerce()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These biMapL(Fn1 fn) {
        return (These) Bifunctor.super.biMapL(fn);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These biMapR(Fn1 fn) {
        return (These) Bifunctor.super.biMapR(fn);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These fmap(Fn1 fn) {
        return MonadRec.super.fmap(fn).coerce();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These zip(Applicative, These> appFn) {
        return MonadRec.super.zip(appFn).coerce();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  Lazy> lazyZip(
            Lazy, These>> lazyAppFn) {
        return projectA().>>fmap(a -> lazy(a(a)))
                .orElseGet(() -> MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These discardL(Applicative> appB) {
        return MonadRec.super.discardL(appB).coerce();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  These discardR(Applicative> appB) {
        return MonadRec.super.discardR(appB).coerce();
    }

    /**
     * Static factory method for wrapping a value of type A in a {@link These}.
     *
     * @param a   the value
     * @param  the first possible type
     * @param  the second possible type
     * @return the wrapped value as a {@link These}<A,B>
     */
    public static  These a(A a) {
        return new _A<>(a);
    }

    /**
     * Static factory method for wrapping a value of type B in a {@link These}.
     *
     * @param b   the value
     * @param  the first possible type
     * @param  the second possible type
     * @return the wrapped value as a {@link These}<A,B>
     */
    public static  These b(B b) {
        return new _B<>(b);
    }

    /**
     * Static factory method for wrapping a value of type A and a value of type B in a {@link
     * These}.
     *
     * @param a   the first value
     * @param b   the second value
     * @param  the first possible type
     * @param  the second possible type
     * @return the wrapped values as a {@link These}<A,B>
     */
    public static  These both(A a, B b) {
        return new Both<>(tuple(a, b));
    }

    /**
     * Convenience method for converting a pair of {@link Maybe}s into a {@link Maybe} of {@link These}. If both
     * {@link Maybe}s are {@link Maybe#just} then the result is a {@link Maybe#just} {@link These#both}. If only one
     * {@link Maybe} is {@link Maybe#just} then it will be {@link Maybe#just} {@link These#a} or
     * {@link Maybe#just} {@link These#b}. If both  {@link Maybe}s are {@link Maybe#nothing} then the result will be
     * {@link Maybe#nothing}.
     *
     * @param maybeA   the first optional value
     * @param maybeB   the second optional value
     * @param  the first possible type
     * @param  the second possible type
     * @return the wrapped values as a {@link Maybe}<{@link These}<A,B>>
     */
    public static  Maybe> fromMaybes(Maybe maybeA, Maybe maybeB) {
        return maybeA.fmap(a -> maybeB.fmap(b -> both(a, b)).orElse(a(a)))
                .fmap(Maybe::just)
                .orElse(maybeB.fmap(These::b));
    }

    /**
     * The canonical {@link Pure} instance for {@link These}.
     *
     * @param  the first possible type
     * @return the {@link Pure} instance
     */
    public static  Pure> pureThese() {
        return These::b;
    }

    private static final class _A extends These {

        private final A a;

        private _A(A a) {
            this.a = a;
        }

        @Override
        public  R match(Fn1 aFn, Fn1 bFn,
                           Fn1, ? extends R> cFn) {
            return aFn.apply(a);
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof These._A && Objects.equals(a, ((_A) other).a);
        }

        @Override
        public int hashCode() {
            return Objects.hash(a);
        }

        @Override
        public String toString() {
            return "These{a=" + a + '}';
        }
    }

    private static final class _B extends These {
        private final B b;

        private _B(B b) {
            this.b = b;
        }

        @Override
        public  R match(Fn1 aFn, Fn1 bFn,
                           Fn1, ? extends R> cFn) {
            return bFn.apply(b);
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof These._B && Objects.equals(b, ((_B) other).b);
        }

        @Override
        public int hashCode() {
            return Objects.hash(b);
        }

        @Override
        public String toString() {
            return "These{b=" + b + '}';
        }
    }

    private static final class Both extends These {
        private final Tuple2 both;

        private Both(Tuple2 tuple) {
            this.both = tuple;
        }

        @Override
        public  R match(Fn1 aFn, Fn1 bFn,
                           Fn1, ? extends R> cFn) {
            return cFn.apply(both);
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof Both && Objects.equals(both, ((Both) other).both);
        }

        @Override
        public int hashCode() {
            return Objects.hash(both);
        }

        @Override
        public String toString() {
            return "These{both=" + both + '}';
        }
    }
}