Appliers

Functor + Applicative = Applier

I looked at Functors and applicatives in the previous blog post. I always found the separation between wrapping a value (functor) and a function to be applied (applicative) to be artificial. What I usually need is to wrap some value (a primitive, an object or a function) and then apply it to some argument.

  • If we are wrapping a value and the argument is a function, we should call the function argument on the value (like a functor).
  • If we are wrapping a function, we should call this function on the argument (like an applicative).

In both cases we should return a wrapped value to allow chaining the future calls.

Applier

I call the wrapped value in this case an Applier. Here is an applier "constructor" function.

1
2
3
4
5
6
7
8
9
10
11
12
13
function Applier(value) {
return function ApplierInner(arg) {
if (typeof value === 'function') {
// behave like Applicative
return Applier(value(arg));
}
if (typeof arg === 'function') {
// behave like Functor
return Applier(arg(value));
}
return ApplierInner;
};
}

This is not real constructor function: Applier is only meant to be a closure to keep value private. Here are typical use cases. There is always a single argument.

1
2
Applier('foo')(length)(console.log); // prints 3
Applier(length)('1234')(console.log); // prints 4

Because applier keeps returning itself, I need to be able to get to the result (wrapped value). I can do this by returning the wrapped value when the applier is called without arguments.

1
2
3
4
5
6
7
8
9
10
function Applier(value) {
return function ApplierInner(arg) {
if (!arguments.length) {
return value;
}
// the rest as above
}
}
console.log(Applier(length)('foobar')());
// prints 6

Applier is chainable, thus I can build a chain of functions before an argument is known.

1
2
3
var lengthOf = Applier(length);
console.log(['12', '12345'].map(lengthOf()));
// prints [2, 5]

Maybe applier

We can add conditions into applier. For example to prevent calling function on undefined value, we can create Maybe applier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Applier(value, conditions) {
conditions = conditions || function () { return true; };
return function ApplierInner(arg) {
if (!arguments.length) {
return value;
}
if (typeof value === 'function' && conditions(arg)) {
// behave like Applicative
return Applier(value(arg), conditions);
}
if (typeof arg === 'function' && conditions(value)) {
// behave like Functor
return Applier(arg(value), conditions);
}
return ApplierInner;
};
}
function MaybeApplier(value) {
function exists(value) {
return value !== null && typeof value !== 'undefined';
}
return Applier(value, exists);
}

Typical use for MaybeApplier is to guard against calling a function on an undefined or null value

1
2
3
4
5
6
7
8
9
10
11
function length(x) {
return x.length;
}
console.log(length());
// TypeError: Cannot read property 'length' of undefined
// instead use MaybeApplier
MaybeApplier('foo')(length)(console.log); // prints 3
MaybeApplier(length)('1234')(console.log); // prints 4
MaybeApplier()(length)(console.log); // does nothing
function nothing() {}
MaybeApplier('foo')(nothing)(length)(console.log); // does nothing

conclusion

Appliers seem to erase the difference between functions and values. In my next blog post I will see how to connect appliers to functional pipelines.