07-21-2023, 03:01 PM
The accepted answer has two drawbacks:
- It misuses `Array.prototype.reduce`, because reducing means to change the structure of a composite type, which doesn't happen in this case.
- It is not particularly reusable
---
### An ES6/ES2015 functional approach
Please note that all functions are defined in curried form.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
// small, reusable auxiliary functions
const keys = o => Object.keys(o);
const assign = (...o) => Object.assign({}, ...o);
const map = f => xs => xs.map(x => f(x));
const mul = y => x => x * y;
const sqr = x => mul(x) (x);
// the actual map function
const omap = f => o => {
o = assign(o); // A
map(x => o[x] = f(o[x])) (keys(o)); // B
return o;
};
// mock data
const o = {"a":1, "b":2, "c":3};
// and run
console.log(omap(sqr) (o));
console.log(omap(mul(10)) (o));
<!-- end snippet -->
- In line A `o` is reassigned. Since Javascript passes reference values [by sharing](
- In line B `map`'s return value is ignored, because `map` performs a mutation of `o`. Since this side effect remains within `omap` and isn't visible in the parent scope, it is totally acceptable.
This is not the fastest solution, but a declarative and reusable one. Here is the same implementation as a one-line, succinct but less readable:
const omap = f => o => (o = assign(o), map(x => o[x] = f(o[x])) (keys(o)), o);
---
### Addendum - why are objects not iterable by default?
ES2015 specified the iterator and iterable protocols. But objects are still not iterable and thus not mappable. [The reason is the mixing of data and program level](
- It misuses `Array.prototype.reduce`, because reducing means to change the structure of a composite type, which doesn't happen in this case.
- It is not particularly reusable
---
### An ES6/ES2015 functional approach
Please note that all functions are defined in curried form.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
// small, reusable auxiliary functions
const keys = o => Object.keys(o);
const assign = (...o) => Object.assign({}, ...o);
const map = f => xs => xs.map(x => f(x));
const mul = y => x => x * y;
const sqr = x => mul(x) (x);
// the actual map function
const omap = f => o => {
o = assign(o); // A
map(x => o[x] = f(o[x])) (keys(o)); // B
return o;
};
// mock data
const o = {"a":1, "b":2, "c":3};
// and run
console.log(omap(sqr) (o));
console.log(omap(mul(10)) (o));
<!-- end snippet -->
- In line A `o` is reassigned. Since Javascript passes reference values [by sharing](
[To see links please register here]
), a shallow copy of `o` is generated. We are now able to mutate `o` within `omap` without mutating `o` in the parent scope.- In line B `map`'s return value is ignored, because `map` performs a mutation of `o`. Since this side effect remains within `omap` and isn't visible in the parent scope, it is totally acceptable.
This is not the fastest solution, but a declarative and reusable one. Here is the same implementation as a one-line, succinct but less readable:
const omap = f => o => (o = assign(o), map(x => o[x] = f(o[x])) (keys(o)), o);
---
### Addendum - why are objects not iterable by default?
ES2015 specified the iterator and iterable protocols. But objects are still not iterable and thus not mappable. [The reason is the mixing of data and program level](
[To see links please register here]
).