# Applicatives: Cooking up Compositions With Ease

Explore Applicatives in functional programming, stepping up from Functors to apply multiple functions to values within a context. Delve into a structured approach for independent computations, setting the stage for advanced abstractions like Monads.

## Overview

This piece is part of a series on functional programming. If you haven't yet, please check out the earlier articles on Algebraic Data Types (ADTs) and Functors to get up to speed.

Stepping into functional programming, we've explored the basics with *Algebraic Data Types (ADTs)* and dived deeper with *Functors*. Now, it's time to add a new tool to our toolkit - *Applicatives*.

*Applicatives*, also known as *Applicative Functors*, sit between Functors and Monads in functional programming.

Unlike *Functors*, which operate in a single context, *Applicatives* allow us to work across multiple contexts. This is like moving from being able to add spices to a single dish to orchestrating the flavors across a multi-course dinner.

Consider a scenario where we are validating multiple fields on a form. With *Functors*, we could apply validation to each field independently. However, if we want to accumulate all errors across fields without stopping at the first one, *Applicatives* are our go-to tool.

In this article, we'll delve into the core of *Applicatives*, explore the laws they adhere to, and look at practical examples to understand their use. We’ll also touch on how they pave the way to Monads, our next stop in this functional journey.

## Understanding Applicatives

Let's build upon the foundation laid by *Functors* to extend our ability to work with context-bound values.

*Applicatives*, also known as *applicative functors*, are a type that allows function application within a context. They can be viewed as an extension of *Functors*. While a *Functor* will enable us to apply a function to a value in a context using the `map`

method, an *Applicative* also facilitates function application but with the ability to handle multiple context-bound values.

### The Interface of an Applicative

To be an applicative an ADT should provide the following methods: `pure`

and `apply`

.

**Pure**takes a value and returns the value wrapped in a context. Putting a value within a context is usually referred to as*lifting*in technical literature.**Apply (**takes a function wrapped in a context and a value wrapped in the same context and returns a new function that takes and returns values wrapped in that context. In technical literature, this is often called lifting the function into the Applicative.`apply`

or`ap`

)

### The Functor Connection

Every *Applicative* is also a *Functor*. This relationship is fundamental as the `map`

method of *Functor* can be defined in terms of `apply`

and `pure`

of Applicative.

This relationship is usually described as "if we get an *Applicative*, we get a *Functor* for free."

Here’s a simplified illustration in Scala-like pseudocode:

```
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
trait Applicative[F[_]] extends Functor[F] {
def apply[A, B](ff: F[A => B]): (F[A] => F[B])
def pure[A](a: A): F[A]
// Implement map using apply and pure
override def map[A, B](fa: F[A])(f: A => B): F[B] =
apply(pure(f))(fa)
}
```

In the above snippet, the *Applicative* trait extends the *Functor* trait, allowing us to pass instances of *Applicative* types to algorithms expecting a *Functor*.

We wrote a default implementation for `map`

using its `apply`

and `pure`

methods. By implementing the *Applicative* in our types, we'll get the *Functor* for free; *Applicatives* extend the capabilities of *Functors* to handle a more sophisticated level of function application within a context.

## Applicative Laws

Like Functors, Applicatives define laws, ensuring all implementations behave consistently and predictably. These laws help us reason about the code and are crucial for understanding the essence of Applicatives.

Besides the Functor laws, the Applicative should obey two more laws. The four laws that any Applicative must satisfy are:

### Identity: `apply(pure(id))(Ax) == Ax`

*Ax* is a value inside an applicative context, and it is the identity function.

This law states that lifting the identity function (`pure(id))`

and applying it to the value inside the context (`Ax`

) should result in precisely the same value.

### Composition: `apply(comp(f, g), Ax) == comp(apply(f), apply(g))(Ax)`

The Functor version of this law says that repeated mapping of functions to a functor is the same as first composing the functions and then mapping the combined function.

In the Applicative, this law asserts the same, but in the applicative context. To understand the definition, we need to define the terms:

*f*and*g*are simple functions not in the*Applicative*context, and their signatures align (`f: A => B`

,`g: B => C`

), allowing us to call*g*with the result of*f*.`comp`

is a function that takes two aligned functions and returns their composition. In our Scala-like pseudo-code, this will look as follows:

```
def comp[A, B, C](f: A => B, g: B => C): A => C = {
a: A => g(f(a))
}
```

Armed with this knowledge, we can interpret the law as lifting the composition of two functions into the Applicative, which is the same as lifting each function individually and then composing the lifted versions.

### Homomorphism: `apply(pure(f))(pure(x)) == pure(f(x))`

If we have a function and a value, both without context, lifting the function and the value to a context and executing the *Applicative* (calling `apply`

), should be the same as executing the function with the given value normally and then lifting the result.

### Interchange: `apply(Af)(pure(x)) == apply(pure(g => g(x))(Af)`

This law states that applying a function within a context to a lifted value is the same as putting the value into a function that takes any function and applies it to x, lifting that function and running the Applicative with it and the original function within the context.

Simply put, it states that the application behaves like a regular function application, just doing it inside the *Applicative* context.

These laws provide a framework for understanding the behavior of Applicatives and allow us to reason about function application in a context in a structured way. They ensure that Applicatives behave predictably, which is crucial for writing reliable and maintainable code.

## Unveiling the Features and Utility of Applicatives

*Applicatives* offer a step up from Functors by allowing multiple function applications within a context. They shine in scenarios where we need to apply several computations to data in a context, and the computations are independent.

One such practical scenario is form validation. Let's consider a web form with two fields: username and email. We want to validate these fields by applying a series of independent validation functions to each field. In Scala, we can model this scenario using an Applicative instance for Lists.

Here's a simplified example:

```
trait Functor[F[_]]:
def fmapply[A, B](fa: F[A])(f: A => B): F[B]
trait Applicative[F[_]] extends Functor[F]:
def pure[A](a: A): F[A]
def apply[A, B](ff: F[A => B])(fa: F[A]): (F[B])
// Implement map using apply and pure
override def fmapply[A, B](fa: F[A])(f: A => B): F[B] =
ap(pure(f))(fa)
// Implement the Applicative instance for Lists
given ListApplicative: Applicative[List] with {
def pure[A](x: A): List[A] = List(x)
def apply[A, B](ff: List[A => B])(fa: List[A]): List[B] =
def helper(ff1: List[A => B], fa1: List[A], acc: List[B]): List[B] = {
(ff1, fa1) match {
case ( Nil, _) => acc
case (f::ffs, Nil) => helper(ffs, fa, acc)
case (f::ffs, a::as) => helper(ff1, as, f(a) :: acc)
}
}
helper(ff, fa, List.empty)
}
case class Form(id: Int, username: String, email: String, age: Int)
type Validated[+A] = Either[String, A]
def validateUsername(form: Form): Validated[Form] =
if (form.username.nonEmpty) Right(form)
else Left(s"Form ${form.id}: Username cannot be empty")
def validateEmail(form: Form): Validated[Form] =
if (form.email.contains("@")) Right(form)
else Left(s"Form ${form.id}: Invalid email")
def validateAge(form: Form): Validated[Form] =
if (form.age >= 18) Right(form)
else Left(s"Form ${form.id}: Age must be at least 18")
def validate(forms: List[Form])(using appl: Applicative[List]): List[Validated[Form]] = {
val validations = List(validateUsername, validateEmail, validateAge)
return appl.ap(validations)(forms)
}
@main def run(): Unit = {
val forms = List(
Form(1, "Alice", "alice@example.com", 25),
Form(2, "", "bob@example.com", 20),
Form(3, "Charlie", "charlie@", 17)
)
val results = validate(forms)
// Display the results
results.foreach(println)
// Left(Form 3: Age must be at least 18)
// Right(Form(2,,bob@example.com,20))
// Right(Form(1,Alice,alice@example.com,25))
// Right(Form(3,Charlie,charlie@,17))
// Right(Form(2,,bob@example.com,20))
// Right(Form(1,Alice,alice@example.com,25))
// Right(Form(3,Charlie,charlie@,17))
// Left(Form 2: Username cannot be empty)
// Right(Form(1,Alice,alice@example.com,25))
}
```

In future articles, we'll learn how to use Scala 3 features to make the use of the Applicative (and similar classes) more idiomatic.

In this setup, we first define a simple *Form* case class to hold the username and email fields. We wrote an *Applicative* trait and implemented it for *Lists*.

Let's take a closer look at our *Applicative* instance. We could explain it as defining how to apply many functions over many values with a single call. Next, we define validation functions for each field we want to verify.

Each function takes a *Form* object and returns a *Validated* with the valid *Form* or an error message. Then we put the functions into a *List*, and run it as an *Applicative* over a *List* of *Forms*.

At the end, we have a *List* of *Validated* *Forms* or the errors found on each form. This way, we achieve a series of independent validations on each form, demonstrating the power of *Applicatives*.

We can use them to handle many computations in a structured and composable manner. Having *Applicatives* in our developer's toolkit allows us to simplify our applications.

This is because we can reuse the design pattern and the code whenever we need to run many functions on some inputs wrapped in an Applicative context.

## Conclusion

Delving into Applicatives has equipped us to apply many functions to values within a context. This step up from *Functors* enables a structured approach to handling independent computations, as illustrated in our form validation example.

The journey from *Functors* to *Applicatives* is like moving from cooking a single dish to preparing a full-course meal.

The pathway to exploring more advanced abstractions like Monads is now visible as we wrap up. But for now, we can appreciate the simplicity and structure *Applicatives* bring to our code.

Stay tuned for the next installment in our series, where we'll unravel the sequencing magic of Monads.

### Addendum: A Special Note for Our Readers

I decided to delay the introduction of subscriptions, you can read the full story here.

In the meantime, I decided to accept donations.

If you can afford it, please consider donating:

Every donation helps me offset the running costs of the site and an unexpected tax bill. Any amount is greatly appreciated.

Also, if you are looking to buy some Swag, please visit I invite you to visit the TuringTacoTales Store on Redbubble.

Take a look, maybe you can find something you like: