Understanding Functor, Applicative, and Monad in JavaScript with Practical Examples
The article explains JavaScript’s Functor, Applicative, and Monad abstractions—showing how map, ap, and chain (flatMap) on Box types such as Either, LazyBox, and a lazy Task can replace nested try‑catch and null checks, enable composable synchronous and asynchronous code, and support parallel execution with Task.ap.
The article continues the series on functional programming in JavaScript, reviewing the concepts of Functor and Applicative and introducing Monad . It shows how these abstractions help to eliminate nested try‑catch and null handling by wrapping values in “Box” types such as Either , LazyBox , and the newly defined Task .
Nested Array Example
const arr = [1, 2, 3, 4]
arr.map(x => [x * 2]) // => [[2],[4],[6],[8]]
arr.flatMap(x => [x * 2]) // => [2,4,6,8]It explains that flatMap is essentially a map followed by a flat operation, removing one level of boxing.
Russian‑Doll Analogy
Using the Either functor, the article demonstrates how to compose functions that may return null without throwing errors. The example builds a composition that extracts a street name from a user object, wrapping each step with Either and finally unwrapping with fold :
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x)
const address = user => user.address
const street = address => address.street
const app = compose(street, address)
const user = { address: { street: '长河' } }
const res = app(user) // => '长河'When the intermediate result is already an Either , using map would add an extra layer of boxing. The solution is to use fold (or chain ) to “flatten” the result.
Task – A Lazy Promise for Asynchronous Operations
The article defines a Task type that stores a fork function. Task.map creates a new task that applies a synchronous function to the resolved value, while Task.chain (or Task.flatMap ) composes tasks that return another task.
const Task = fork => ({
map: f => Task((reject, resolve) => fork(reject, x => resolve(f(x)))),
chain: f => Task((reject, resolve) => fork(reject, x => f(x).fork(reject, resolve))),
fork,
inspect: () => 'Task(?)'
})Using Task , the synchronous readConfig example becomes:
const readConfig = filepath =>
Task((reject, resolve) =>
tryCatch(() => fs.readFileSync(filepath))
.chain(json => tryCatch(() => JSON.parse(json)))
.fold(() => '0.0.0', c => c.version)
)And an asynchronous version using Task :
const readConfig = filepath =>
Task((reject, resolve) =>
fs.readFile(filepath, (err, data) => err ? reject(err) : resolve(data))
)
const writeConfig = (filepath, contents) =>
Task((reject, resolve) =>
fs.writeFile(filepath, contents, (err, _) => err ? reject(err) : resolve(contents))
)
const app = readConfig('config.json')
.map(JSON.parse)
.map(c => ({ version: c.version + 1 }))
.map(JSON.stringify)
.chain(c => writeConfig('config.json', c))
app.fork(() => console.log('something went wrong'), () => console.log('read and write config success'))The article compares Task with native Promise , noting that a Promise starts executing immediately, whereas a Task is lazy and only runs when fork is called.
Applicative Parallel Composition
For operations that do not depend on each other, the article shows how to use Task.ap (similar to Promise.all ) to run them in parallel:
Task.of(name => age => ({ name, age }))
.ap(getUser)
.ap(getAge)
.fork(console.error, console.log) // after 2000 ms logs {name:'Melo', age:18}Finally, the article summarises the differences between Functor, Applicative, and Monad:
Functor implements map – applying a function to a wrapped value.
Applicative implements ap – applying a wrapped function to a wrapped value.
Monad implements chain (or flatMap ) – applying a function that returns a wrapped value and flattening the result.
References to external articles, specifications, and source code repositories are listed at the end of the original document.
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.