Pure Functions, Functors, and Applicative Functors with LazyBox in JavaScript
The article introduces pure functions and shows how JavaScript functors like Box can lazily transform values with map, uses LazyBox to defer side effects until fold, then extends the concept to applicative functors and Either for composable validation via ap and liftA2 helpers.
This article explains the concepts of pure functions, functors, and applicative functors in the context of JavaScript functional programming. It builds on the previous chapter’s introduction of Functor and shows how a value can be placed inside a Box and transformed with the map method (e.g., Box(1).map(x => x+1) ).
Pure functions are defined by two constraints: (1) given the same arguments they always return the same result, and (2) they produce no observable side effects during execution. The article discusses why side effects are unavoidable in typical JavaScript code (DOM manipulation, I/O, etc.) and why they must be isolated.
To keep most code pure while still handling side effects, the article introduces a LazyBox implementation. A lazy box stores a function without evaluating it until fold is called. Example code:
const LazyBox = g => ({
map: f => LazyBox(() => f(g())),
fold: f => f(g())
});
const finalPrice = str =>
LazyBox(() => str)
.map(x => { console.log('str:', str); return x; })
.map(x => x * 2)
.map(x => x * 0.8)
.map(x => x - 50);
const res = finalPrice(100).fold(x => x);
console.log(res); // => 110The map chain remains pure; only when fold is invoked does the side‑effect (logging) occur.
The article then connects this idea to Applicative Functors . An applicative functor can apply a function that is itself wrapped in a context to a wrapped value: F(f).ap(F(x)) == F(x).map(f) . Using the same Box abstraction, the article shows how to apply a curried function to multiple boxed arguments:
const Box = x => ({
map: f => Box(f(x)),
apply: o => o.map(x),
fold: f => f(x),
inspect: () => `Box(${x})`
});
const addOne = x => x + 1;
Box(addOne).apply(Box(2)); // => Box(3)It also demonstrates how Either (Right/Left) can be extended for form validation, separating successful and error branches. The validation functions return Right(true) on success or Left(errorMessage) on failure. By lifting a curried success function into Right and using ap , multiple field checks can be combined without nesting:
const Right = x => ({
map: f => Right(f(x)),
ap: o => o.isLeft ? o : o.map(x),
fold: (f, g) => g(x),
isLeft: false,
isRight: true,
inspect: () => `Right(${x})`
});
const Left = x => ({
map: f => Left(x),
ap: o => o.isLeft ? Left(x.concat(o.x)) : Left(x),
fold: (f, g) => f(x),
isLeft: true,
isRight: false,
inspect: () => `Left(${x})`
});
const checkName = name => /^[0-9].+$/.test(name) ? Left('用户名不能以数字开头') : Right(true);
const checkPW = pw => pw.length <= 6 ? Left('密码长度必须大于6位') : Right(true);
const checkUserInfo = user => {
const { name, pw } = user;
const success = () => true;
const returnSuccess = R.curryN(2, success);
return Right(returnSuccess).ap(checkName(name)).ap(checkPW(pw));
};To avoid hard‑coding the container type, the article introduces a point‑free helper liftA2 (and its higher‑arity variants) that works with any applicative functor:
const liftA2 = (g, f1, f2) => f1.map(g).ap(f2);
const liftA3 = (g, f1, f2, f3) => f1.map(g).ap(f2).ap(f3);Using liftA2 , the validation function becomes concise:
function checkUserInfo(user) {
const { name, pw } = user;
const returnSuccess = R.curryN(2, () => true);
return liftA2(returnSuccess, checkName(name), checkPW(pw));
}The article concludes by summarizing the relationship between functors and applicative functors: a functor can map a single‑argument function, while an applicative functor can apply a wrapped multi‑argument (curried) function to multiple wrapped values, enabling powerful composition patterns such as form validation. It also outlines the next topic—Monads—for handling asynchronous composition in JavaScript.
NetEase Cloud Music Tech Team
Official account of NetEase Cloud Music Tech Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.