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
.
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]))
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)
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)
For a full list of other useful combinators available in Applicative
, see the Source
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)
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)
Sequences actions, discarding the value of the first argument.
Option.applicative().run { Some(1).followedBy(Some(2)) }
// Option.Some(2)
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)
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.
Arrow provides ApplicativeLaws
in the form of test cases for internal verification of lawful instances and third party apps creating their own Applicative instances.
Applicative
instancesArrow 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.
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?
✖