Applicative

The Applicative typeclass abstracts the ability to lift values and apply functions over the computational context of a type constructor. Examples of type constructors that can implement instances of the Applicative typeclass include Option, NonEmptyList, List, and many other datatypes that include a just and either ap function. ap may be derived for monadic types that include a Monad instance via flatMap.

Applicative includes all combinators present in Functor.

Applicative Builder examples

We often find ourselves in situations where we need to compute multiple independent values resulting from operations that do not depend on each other.

In the following example, we will define three invocations that may as well be remote or local services—each one of them returning different results in the same computational context of Option.

import arrow.*
import arrow.core.*

fun profileService(): Option<String> = Some("Alfredo Lambda")
fun phoneService(): Option<Int> = Some(55555555)
fun addressService(): Option<List<String>> = Some(listOf("1 Main Street", "11130", "NYC"))

This more or less illustrates the common use case of performing several independent operations where we need to get all the results together.

The typeclass features several methods related to Applicative Builders that allow you to easily combine all the independent operations into one result.

import arrow.core.extensions.option.applicative.*

data class Profile(val name: String, val phone: Int, val address: List<String>)

val r: Option<Tuple3<String, Int, List<String>>> = Option.applicative().tupled(profileService(), phoneService(), addressService()).fix()
r.map { Profile(it.a, it.b, it.c) }
// Option.Some(Profile(name=Alfredo Lambda, phone=55555555, address=[1 Main Street, 11130, NYC]))

The Applicative Builder also provides a map operations that is able to abstract over arity in the same way as tupled.

Option.applicative().map(profileService(), phoneService(), addressService(), { (name, phone, addresses) ->
  Profile(name, phone, addresses)
})
// Option.Some(Profile(name=Alfredo Lambda, phone=55555555, address=[1 Main Street, 11130, NYC]))

Main Combinators

just

A constructor function, also known as pure in other languages. It lifts a value into the computational context of a type constructor.

fun <A> just(a: A): Kind<F, A>

Option.just(1) // Some(1)
// Option.Some(1)

Kind<F, A>#ap

Apply a function inside the type constructor’s context.

fun <A, B> Kind<F, A>.ap(ff: Kind<F, (A) -> B>): Kind<F, B>

Option.applicative().run { Some(1).ap(Some({ n: Int -> n + 1 })) }
// Option.Some(2)

Other combinators

For a full list of other useful combinators available in Applicative, see the Source

Kind<F, A>#map2

Map two values inside the type constructor context and apply a function to their cartesian product.

Option.applicative().run { Some(1).map2(Some("x")) { z: Tuple2<Int, String> ->  "${z.a}${z.b}" } }
// Option.Some(1x)

Kind<F, A>#map2Eval

Lazily map two values inside the type constructor context and apply a function to their cartesian product. Computation happens when .value() is invoked.

Option.applicative().run { Some(1).map2Eval(Eval.later { Some("x") }, { z: Tuple2<Int, String> ->  "${z.a}${z.b}" }).value() }
// Option.Some(1x)

Kind<F, A>#followedBy

Sequences actions, discarding the value of the first argument.

Option.applicative().run { Some(1).followedBy(Some(2)) }
// Option.Some(2)

Kind<F, A>#apTap

This is a reverse for followedBy. Sequences actions but discarding the value of the second argument.

Option.applicative().run { Some(1).apTap(Some(2)) }
// Option.Some(1)

Apply

A closely related type class is Apply, which is identical to Applicative, modulo the just method. Indeed, Applicative is a subclass of Apply with the addition of this method.

import arrow.typeclasses.Functor

interface Apply<F> : Functor<F> {
  fun <A, B> Kind<F, A>.ap(ff: Kind<F, (A) -> B>): Kind<F, B>
}

interface Applicative<F> : Apply<F> {
  fun <A> just(a: A): Kind<F, A>
}

One of the motivations for Apply’s existence is that some types have Apply instances but not Applicative.

Laws

Arrow provides ApplicativeLaws in the form of test cases for internal verification of lawful instances and third party apps creating their own Applicative instances.

Creating your own Applicative instances

Arrow already provides Applicative instances for most common datatypes both in Arrow and the Kotlin stdlib.

See Deriving and creating custom typeclass to provide your own Applicative instances for custom datatypes.

Data types

Additionally, all instances of Monad and their MTL variants implement the Applicative typeclass directly since they are all subtypes of Applicative.

Do you like Arrow?

Arrow Org
<