Functional Patterns — Currying, Composition, Immutability
JS অর্ধেক functional — সেই অর্ধেকটি শক্তিশালী
1. Pure Functions
A pure function (a) returns the same output for the same input and (b) has no side effects (no mutation, no I/O, no random). Pure functions are easy to test, reason about, cache, and parallelize.
// Pure
const add = (a, b) => a + b;
// Impure — depends on hidden state
let count = 0;
const tick = () => ++count;
// Impure — mutates input
const push = (arr, x) => { arr.push(x); return arr; };
// Pure refactor — return a new array
const append = (arr, x) => [...arr, x];
const a = [1, 2];
console.log(append(a, 3), a); // [1,2,3] [1,2]
2. Immutability
const user = { name: "Arif", age: 22 };
// Update — return new object
const older = { ...user, age: 23 };
const nums = [1, 2, 3];
const next = [...nums, 4];
const noTwo = nums.filter(n => n !== 2);
console.log(user, older);
console.log(nums, next, noTwo);
// Object.freeze — shallow lock
const CFG = Object.freeze({ debug: false });
Libraries like Immer give you "mutable-looking" code that produces immutable updates under the hood — easier to write, same correctness.
3. Currying & Partial Application
// Manual curry
const add = a => b => c => a + b + c;
console.log(add(1)(2)(3));
// Partial application — fix some args, leave the rest
const partial = (fn, ...preset) => (...rest) => fn(...preset, ...rest);
const log = (level, msg) => console.log(`[${level}] ${msg}`);
const info = partial(log, "INFO");
info("server up");
info("port 3000");
4. Composition: compose & pipe
// pipe — left to right (more readable)
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
// compose — right to left (math style)
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const trim = s => s.trim();
const lower = s => s.toLowerCase();
const dasherize = s => s.replace(/\s+/g, "-");
const slug = pipe(trim, lower, dasherize);
console.log(slug(" Hello Bangla World "));
5. Map / Filter / Reduce as Building Blocks
const users = [
{ name: "Arif", age: 22, active: true },
{ name: "Karim", age: 35, active: false },
{ name: "Nusrat", age: 28, active: true },
];
const avgAgeOfActive = users
.filter(u => u.active)
.map(u => u.age)
.reduce((a, b, _, arr) => a + b / arr.length, 0);
console.log(avgAgeOfActive);
6. Persistent Data — Quick Mention
Real production FP uses structural sharing — when you "update" an object, you reuse all unchanged subtrees. Libraries like Immer, Immutable.js, or Zustand patches do this for you. The benefit: cheap equality checks via reference, and ergonomic React/Redux state.
7. When NOT to Go Full FP in JS
- Performance-critical inner loops — direct mutation can be 10× faster
- DOM code where mutation is the model the browser exposes
- Stream processing with native callback APIs
Use FP where it shines (data transformation, state updates, validation), and pragmatic OOP/imperative everywhere else.
8. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Pure function | Same input → same output, no side effects. | একই input → একই output, কোনো side effect নেই। |
| Side effect | Mutation, I/O, randomness, time. | Mutation, I/O, random, time। |
| Immutability | Don't mutate; return new value instead. | Mutate না করে নতুন value ফেরত দেওয়া। |
| Currying | Transform f(a, b) into f(a)(b). | f(a, b)-কে f(a)(b)-তে রূপান্তর। |
| Partial application | Pre-fix some args of a function. | কিছু argument আগেই দেওয়া। |
| Composition | compose(f, g)(x) = f(g(x)) — right to left. | f(g(x)) — ডান থেকে বামে। |
| Pipe | pipe(f, g)(x) = g(f(x)) — left to right. | বাম থেকে ডানে — পড়তে সহজ। |
| Referential transparency | An expression can be replaced by its value without changing behaviour. | Expression-কে তার value দিয়ে replace করা যায়। |
| Structural sharing | "Update" reuses unchanged subtrees — cheap immutable updates. | অপরিবর্তিত অংশ পুনঃব্যবহার — সস্তা immutable update। |
9. Practice Problems
- Write a pure
incrementthat doesn't touch its argument.✨ Show Answer
a1.jsconst increment = ({ count }) => ({ count: count + 1 }); const s = { count: 5 }; console.log(increment(s), s); - Curry
addsoadd(2)(3)(4)returns 9.✨ Show Answer
a2.jsconst add = a => b => c => a + b + c; console.log(add(2)(3)(4)); - Build pipe and chain three transformations.
✨ Show Answer
a3.jsconst pipe = (...fns) => x => fns.reduce((v, f) => f(v), x); console.log(pipe(n => n + 1, n => n * 2, n => n - 3)(5)); - Update the
agefield of an object immutably.✨ Show Answer
a4.jsconst u = { name: "Arif", age: 22 }; console.log({ ...u, age: 23 }, u); - Remove an element from an array immutably.
✨ Show Answer
a5.jsconst remove = (arr, x) => arr.filter(v => v !== x); console.log(remove([1, 2, 3, 4], 3)); - Why do React-style apps prefer immutable updates?
✨ Show Answer
Answer: Reference equality becomes a cheap "did anything change?" check. Components only re-render when their props or state objects change identity, so
{ ...state, x: 1 }triggers exactly the right work — much faster than deep comparison and easier to reason about than mutation-tracking proxies. - Build
partialand use it to createinfo,warn,errorfrom a generic log.✨ Show Answer
a7.jsconst partial = (fn, ...p) => (...r) => fn(...p, ...r); const log = (level, msg) => console.log(`[${level}] ${msg}`); ["INFO", "WARN", "ERROR"].forEach(l => partial(log, l)("hello")); - Compose three string transforms to make a slug.
✨ Show Answer
a8.jsconst pipe = (...fns) => x => fns.reduce((v, f) => f(v), x); const slug = pipe(s => s.trim(), s => s.toLowerCase(), s => s.replace(/\s+/g, "-")); console.log(slug(" Hello Bangla World ")); - Demonstrate that
Object.freezeis shallow.✨ Show Answer
a9.jsconst o = Object.freeze({ inner: { x: 1 } }); o.inner.x = 99; console.log(o.inner); // { x: 99 } - Implement deepFreeze recursively.
✨ Show Answer
a10.jsconst deepFreeze = o => { if (o && typeof o === "object") { Object.values(o).forEach(deepFreeze); Object.freeze(o); } return o; }; const c = deepFreeze({ a: { b: 1 } }); try { c.a.b = 2; } catch {} console.log(c.a.b); - Map an array of strings to their lengths using map.
✨ Show Answer
a11.jsconsole.log(["hi", "there", "hello"].map(s => s.length)); - Build a curried filter that filters by predicate.
✨ Show Answer
a12.jsconst filter = pred => arr => arr.filter(pred); const evens = filter(n => n % 2 === 0); console.log(evens([1, 2, 3, 4])); - In one paragraph, explain when FP shines vs when imperative is better.
✨ Show Answer
Answer: FP shines for data transformation pipelines, validation, state-update reducers, and anywhere correctness over speed matters. Imperative code wins for tight inner loops, DOM updates, parser/state-machine work, and any case where a single in-place mutation expresses intent more directly than a stack of pure transformers. Most modern apps mix both — pure for app state, imperative for hot paths.
- Show why
arr.push+ return arr is impure.✨ Show Answer
The function modifies the caller's array — same input now produces different output across calls. Anyone holding a reference observes the change, leading to "spooky action at a distance" bugs. Use
[...arr, x]for the immutable equivalent.
Summary — Module 33
Pure functions + immutable updates eliminate whole classes of bugs and play perfectly with React-style frameworks. Currying, partial application, and composition are the toolset. Use pipe over deeply nested calls. Object.freeze is shallow — write your own deep freeze if you need it.