Concepts: Applicative

1. Motivation

I've demonstrated the motivation and some use cases behind both Functor and Applicative. They let me apply functions to values in the context of a Consumer. When doing so, all the arguments for the function must be in a Consumer context. There might be some times though when that's not the case. I might need to apply a function to a constant value. Or I might need to dynamically construct consumers based on some parameters.

Suppose I have a component for rendering lists, and I want to wire it up to a Redux store using a selector based on the type of list (todo or grocery). One way I might do that is with a factory function that returns the selector.

{applicative-introduction-1 1}
const listSelectorFactory = (type) =>
  (state, props) => state[type];

This works, although there's a little bit of boilerplate. This would also get more complicated if the selector relied on some other information from the store. For example, if there were some stored user preferences changing the list sorting order.

{applicative-introduction-2 1}
const listSelectorFactory = (type) =>
    (state, props) =>
        sortedListItems(state[type], state.sort)

The goal of Applicative is to simplify this by allowing the creation of "constant" consumers.

2. Details

The Applicative interface is simple. The of method is required by the static-land specification. I've aliased it as constant to provide a name that is potentially easier to remember.

{types/Applicative.js 2}
export type Applicative<Static, In, Out> = {
  of: (Out) => (In, Static) => Out,
  constant: (Out) => (In, Static) => Out
};

There is of course the generic type parameters. Contrary to many of the other interfaces, there are only three generics here, and none of them are duplicated. This knowledge tells me and the Flow checker that none of these parameters can change. The of function has a signature very similar to that of a constant function ({constant,12:3}), and that's a clue that it works similarly.

There are some laws that the Applicative interface must follow. They are related to and depend on the Apply interface. The first law is the Identity law, which simply says that using of on the identity function ({identity,12:3}) and applying it to a value in a consumer is the same as just using that consumer.

{applicative-laws-identity 2}
ap(of(identity), consumer) === consumer

The next law is the homomorphism law, which says simply that using of on a function and all of its arguments is the same as just calling that function on its arguments directly and using of on the result.

{applicative-laws-homomorphism 2}
ap(of(f), of(x)) === of(f(x))

The final law is the interchange law. It is more complicated, but essentially it ensures that of has no side effects other than lifting its argument into the consumer context.

{applicative-laws-interchange 2}
ap(u, of(y)) === ap(of(f => f(y)), u)

With all those laws and the interface, the implementation is pretty straightforward. It's essentially the same as {constant,12:3} but with consumers in mind.

{applicative-implementation 2}
const of = (x) =>
  (_state, _props) =>
    x

That's it. I need to wrap it up in a module and then this interface is finished.

{Applicative.js 2}
{import-types-applicative, 3}

{applicative-implementation, 2}
const ApplicativeI : Applicative<*, *, *> = {
  of: of,
  constant: of
};

export default ApplicativeI;

3. Imports

The Applicative module must import the Applicative type definition.

{import-types-applicative 3}
import type { Applicative } from './types/Applicative'

Used in section 2

Other modules need to import Applicative

{import-applicative 3}
import Applicative from './Applicative';

Used in sections 6:2, 12:1 and 12:2


Previous ChapterNext Chapter