flix

0.79.0

Applicative.flix

/*
 *  Copyright 2021 Felix Wiemuth
 *
 * Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE.md file.
 */

///
/// Trait for types that support embedding values and applying embedded functions, and its functions.
///
pub mod Applicative {

    ///
    /// Trait for types that support embedding values and applying embedded functions.
    ///
    /// An applicative is a functor that allows one to:
    ///
    /// - Make an applicative value out of a normal value (embed it into a default context), e.g. embed `5` into `Some(5)`.
    /// - Apply a function-type applicative to a matching argument-type applicative, resulting in an applicative of
    /// the function's result type.
    ///
    /// The meaning of the application realized by the `ap` function is defined by the respective instance.
    /// Conceptually this can be understood as applying functions "contained" in the first applicative to arguments
    /// in the second applicative, where the possible quantity of functions/arguments depends on the type `m`.
    /// For example, an `Option[a -> b]` can be `None`, or contain a function of type `a -> b`, and only in the
    /// latter case a function is applied. A `List[a -> b]` is an applicative that contains a list of functions,
    /// which are all to be applied to all arguments contained in the arguments list.
    ///
    /// A minimal implementation must define `point` and at least one of `ap` and `map2` (if `map2` is implemented,
    /// `ap` can be defined based on `map2` as shown below). If both `ap` and `map2` are defined,
    /// they must be equivalent to their default definitions:
    ///     `ap(f: m[a -> b \ e], x: m[a]): m[b] \ ef = map2(identity, f, x)`
    ///     `map2(f: a -> b -> c \ e, x: m[a], y: m[b]): m[c] \ ef = ap(Functor.map(f, x), y)`
    ///
    pub trait Applicative[m: Type -> Type] with Functor[m] {
        ///
        /// Puts `x` into a default context.
        ///
        pub def point(x: a): m[a]

        ///
        /// Apply the function-type applicative `f` to the argument-type applicative `x`.
        ///
        pub def ap(f: m[a -> b \ ef], x: m[a]): m[b] \ ef

        ///
        /// Lift a binary function to work on `Applicative`s.
        /// Instances can define more efficient implementations than the default implementation
        /// (which is `Applicative.ap(Functor.map(f, x1), x2)`).
        ///
        pub def map2(f: t1 -> t2 -> r \ ef, x1: m[t1], x2: m[t2]): m[r] \ ef = Applicative.ap(Functor.map(f, x1), x2)

        ///
        /// Lift a ternary function to work on `Applicative`s.
        /// Instances can define more efficient implementations than the default implementation
        /// (which is `Applicative.ap(Applicative.map2(f, x1, x2), x3)`).
        ///
        pub def map3(f: t1 -> t2 -> t3 -> r \ ef, x1: m[t1], x2: m[t2], x3: m[t3]): m[r] \ ef = Applicative.ap(Applicative.map2(f, x1, x2), x3)

        ///
        /// Lift a 4-ary function to work on `Applicative`s.
        /// Instances can define more efficient implementations than the default implementation
        /// (which is `Applicative.ap(Applicative.map3(f, x1, x2, x3), x4)`).
        ///
        pub def map4(f: t1 -> t2 -> t3 -> t4 -> r \ ef, x1: m[t1], x2: m[t2], x3: m[t3], x4: m[t4]): m[r] \ ef = Applicative.ap(Applicative.map3(f, x1, x2, x3), x4)

        ///
        /// Lift a 5-ary function to work on `Applicative`s.
        /// Instances can define more efficient implementations than the default implementation
        /// (which is `Applicative.ap(Applicative.map3(f, x1, x2, x3), x4)`).
        ///
        pub def map5(f: t1 -> t2 -> t3 -> t4 -> t5 -> r \ ef, x1: m[t1], x2: m[t2], x3: m[t3], x4: m[t4], x5: m[t5]): m[r] \ ef = Applicative.ap(Applicative.map4(f, x1, x2, x3, x4), x5)
    }

    ///
    /// Chain two applicative actions, returns the product of their results.
    ///
    pub def product(fa: m[a], fb: m[b]): m[(a, b)] with Applicative[m] =
        Applicative.map2((a, b) -> (a, b), fa, fb)

    ///
    /// Chain two applicative actions, return only the result of the first.
    ///
    pub def productLeft(fa: m[a], fb: m[b]): m[a] with Applicative[m] =
        Applicative.map2((a, _) -> a, fa, fb)

    ///
    /// Chain two applicative actions, return only the result of the second.
    ///
    pub def productRight(fa: m[a], fb: m[b]): m[b] with Applicative[m] =
        Applicative.map2((_, b) -> b, fa, fb)

    ///
    /// Chain three applicative actions, return the 3-tuple of their results.
    ///
    pub def product3(x1: m[t1], x2: m[t2], x3: m[t3]): m[(t1, t2, t3)] with Applicative[m] =
        Applicative.map3((a, b, c) -> (a, b, c), x1, x2, x3)

    ///
    /// Chain four applicative actions, return the 4-tuple of their results.
    ///
    pub def product4(x1: m[t1], x2: m[t2], x3: m[t3], x4: m[t4]): m[(t1, t2, t3, t4)] with Applicative[m] =
        Applicative.map4((a, b, c, d) -> (a, b, c, d), x1, x2, x3, x4)

    ///
    /// Chain five applicative actions, return the 5-tuple of their results.
    ///
    pub def product5(x1: m[t1], x2: m[t2], x3: m[t3], x4: m[t4], x5: m[t5]): m[(t1, t2, t3, t4, t5)] with Applicative[m] =
        Applicative.map5((a, b, c, d, e) -> (a, b, c, d, e), x1, x2, x3, x4, x5)

    ///
    /// `<*>` is an operator alias for `ap`.
    ///
    pub def <*>(mf: m[a -> b \ ef], ma: m[a]): m[b] \ ef with Applicative[m] = Applicative.ap(mf, ma)

    ///
    /// `<**>` is a variant of the operator `<*>` with its arguments flipped.
    ///
    /// The order of evaluation is `ma` then `mf`.
    ///
    pub def <**>(ma: m[a], mf: m[a -> b \ ef]): m[b] \ ef with Applicative[m] = Applicative.map2((a, f) -> f(a), ma, mf)

    ///
    /// `<*` is an operator alias for `productLeft`.
    ///
    pub def <*(ma: m[a], mb: m[b]): m[a] with Applicative[m] = productLeft(ma, mb)

    ///
    /// `*>` is an operator alias for `productRight`.
    ///
    pub def *>(ma: m[a], mb: m[b]): m[b] with Applicative[m] = productRight(ma, mb)

}