With an array we can use the .map
method to create a new map with the same array structure (ie. the same number of elements).
eg.
const array = [2, 4, 6];
const newArray = array.map(v => v *2); //[4, 8, 12];
I believe in functional programming this makes the array object what is called a functor .
With objects I want to do something similar - I want to create a new object with the same structure (same keys), as well as the same functions etc as the original object.
eg.
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const newObj = obj.map(v => v *2); // {foo: 4, bar: 8, biz: 12}
The way I'm currently doing this is by using Object.entries
and .reduce
:
const obj = { foo: 2, bar: 4, biz: 6 }; const newObj = Object.entries(obj).reduce((acc,cur) => { return {...acc, [cur[0]]: cur[1] * 2 } }, {}); console.log(newObj);
I'm wondering - is there currently an object method that would let me do this that I'm missing? Or a proposal to add one?
There is no native map for objects, but can easily make one. I think entries is the simplest.
const obj = { foo: 2, bar: 4, biz: 6 } const objMap = (obj, fn) => Object.fromEntries( Object.entries(obj).map( ([k, v], i) => [k, fn(v, k, i)] ) ) console.log( objMap(obj, v => v * 2), objMap(obj, (v, k) => `${k}-${v}`), objMap(obj, (v, _, i) => v * i) )
The map
function for objects is easy enough to define.
Object.defineProperty(Object.prototype, "map", { value(mapping) { const oldEntries = Object.entries(this); const newEntries = oldEntries.map(([key, val]) => [key, mapping(val)]); return Object.fromEntries(newEntries); } }); const obj = { foo: 2, bar: 4, baz: 6 }; const result = obj.map(x => 2 * x); console.log(result);
Note that this is different from kornieff's objMap
function because the mapping function can't access or change the key. Hence, it's the correct implementation of the Functor
type class for objects.
As a bonus, let's implement some other useful type classes for objects. First, the Representable
type class.
Object.tabulate = table => new Proxy({}, { get: (_, key) => table(key) }); const object = Object.tabulate(key => { if (key === "foo") return 10; if (key === "bar") return 20; return 30; }); console.log(object.foo); // 10 console.log(object.bar); // 20 console.log(object.baz); // 30
The tabulate
function can be used to define various useful type classes such as Monad
and Comonad
. For example, let's implement the Distributive
type class. I'll leave the implementation of the other type classes as an exercise for the reader.
Object.tabulate = table => new Proxy({}, { get: (_, key) => table(key) }); const distribute = functor => Object.tabulate(key => functor.map(object => object[key])); const result1 = distribute([ { foo: 10, bar: 20 }, { foo: 30, bar: 40 } ]); console.log(result1.foo); // [ 10, 30 ] console.log(result1.bar); // [ 20, 40 ] Object.defineProperty(Object.prototype, "map", { value(mapping) { const oldEntries = Object.entries(this); const newEntries = oldEntries.map(([key, val]) => [key, mapping(val)]); return Object.fromEntries(newEntries); } }); const result2 = distribute({ a: { foo: 10, bar: 20 }, b: { foo: 30, bar: 40 } }); console.log(result2.foo); // { a: 10, b: 30 } console.log(result2.bar); // { a: 20, b: 40 }
Note that distribute
expects an object that has a map
method, which is why we defined the map
method on Object.prototype
. However, we could have used the Yoneda
lemma to get around this restriction.
One way to do it:
const obj = { foo: 2, bar: 4, biz: 6 }; const x = Object.keys(obj).map(o => { return {[o]: obj[o] * 2} }) console.log(JSON.stringify(x, null, 2))
Another way to do it using .reduce()
is like this, making use of destructuring to read a little bit easier ( I think )
const obj = { foo: 2, bar: 4, biz: 6 }; const newObj = Object.entries(obj).reduce( (acc, [key, value]) => ({...acc, [key]: value * 2}), {} ); console.log(newObj)
If you fancy experimenting a bit of functional apis in your code, then you could consider to use a functional library (such as Ramda),
It doesn't only introduce the functor api for objects (map), but also other cool stuff!
const input = { a: 1, b: 44, c: 77 }; const doubled = R.map(R.multiply(2), input); console.log( `doubling input`, doubled, ); // and a lot more... const odds = R.filter(R.modulo(R.__, 2), input); console.log( `filtering odds only of input`, odds, );
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
you can use the function mapValues of lodash
const array = [2, 4, 6];
_.mapValues(array, v => v * 2)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.