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

com.jnape.palatable.lambda.adt.choice.Choice2 Maven / Gradle / Ivy

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

import com.jnape.palatable.lambda.adt.Either;
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.lambda.adt.coproduct.CoProduct2;
import com.jnape.palatable.lambda.adt.hlist.HList;
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.specialized.Pure;
import com.jnape.palatable.lambda.functor.Applicative;
import com.jnape.palatable.lambda.functor.Bifunctor;
import com.jnape.palatable.lambda.functor.Functor;
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.functions.builtin.fn2.Into.into;
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate;
import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline;
import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;

/**
 * Canonical ADT representation of {@link CoProduct2}. Unlike {@link Either}, there is no concept of "success" or
 * "failure", so the domain of reasonable function semantics is more limited.
 *
 * @param  the first possible type
 * @param  the second possible type
 * @see Either
 * @see Choice3
 */
public abstract class Choice2 implements
        CoProduct2>,
        MonadRec>,
        Bifunctor>,
        Traversable> {

    private Choice2() {
    }

    /**
     * Specialize this choice's projection to a {@link Tuple2}.
     *
     * @return a {@link Tuple2}
     */
    @Override
    public Tuple2, Maybe> project() {
        return into(HList::tuple, CoProduct2.super.project());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final  Choice3 diverge() {
        return match(Choice3::a, Choice3::b);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Choice2 invert() {
        return match(Choice2::b, Choice2::a);
    }

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

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

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

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

    /**
     * {@inheritDoc}
     */
    @Override
    public  Choice2 pure(C c) {
        return b(c);
    }

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

    /**
     * {@inheritDoc}
     */
    @Override
    public  Lazy> lazyZip(
            Lazy, Choice2>> lazyAppFn) {
        return match(a -> lazy(a(a)),
                     b -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(b)).coerce()));
    }

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

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

    /**
     * {@inheritDoc}
     */
    @Override
    public final  Choice2 flatMap(Fn1>> f) {
        return match(Choice2::a, b -> f.apply(b).coerce());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  Choice2 trampolineM(Fn1, Choice2>> fn) {
        return match(Choice2::a,
                     trampoline(b -> fn.apply(b).>>coerce()
                             .match(a -> terminate(a(a)),
                                    bOrC -> bOrC.fmap(Choice2::b))));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public , TravB extends Traversable>,
            AppTrav extends Applicative> AppTrav traverse(Fn1> fn,
                                                                      Fn1 pure) {
        return match(a -> pure.apply(Choice2.a(a).coerce()),
                     b -> fn.apply(b).>fmap(Choice2::b).fmap(Functor::coerce).coerce());
    }

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

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

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

    private static final class _A extends Choice2 {

        private final A a;

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

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

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

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

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

    private static final class _B extends Choice2 {

        private final B b;

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

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

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

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

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