點燈坊

失くすものさえない今が強くなるチャンスよ

如何對 Array 中的 Object 以指定值新增 Property ?

Sam Xiao's Avatar 2020-02-20

實務上較少更改 map() 所回傳的 object,若要新增 property 可使用 assoc()

Version

macOS Catalina 10.15.3
VS Code 1.42.1
Quokka 1.0.277
Ramda 0.27.0

Imperative

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = k => v => arr => {
  let result = []

  for(let x of arr) {
    let obj = Object.assign({}, x)
    obj[k] = v
    result.push(obj)
  }

  return result
}

f('author')('John Doe')(data) // ?

Imperative 會使用 for loop,先使用 Object.assign() clone object,在搭配 [] 以指定值對 object 新增 property,最後將 obj push 進 result array 回傳。

Imperative 會先建立 result empty array,使用 for loop 對 data 一筆一筆處理,然後使用 Object.assign() clone object,在搭配 [] 以指定值對 object 新增 property,最後將 obj push 進 result array 回傳。

assoc000

ECMAScript

reduce()

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = k => v => arr => arr.reduce((a, x) => [...a, {...x, [k]: v}], [])

f('author')('John Doe')(data) // ?

只要能使用 for loop,就能使用 reduce() 改寫。

assoc007

map()

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = k => v => arr => arr.map(x => ({...x, [k]: v}))

f('author')('John Doe')(data) // ?

ECMAScript 可使用 map() 與 object spread 新增 property。

assoc001

Ramda

import { map } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = k => v => map(x => ({...x, [k]: v}))

f('author')('John Doe')(data) // ?

可使用 Ramda 的 map() 使 arr argument point-free。

assoc002

assoc()

import { map, assoc } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = k => v => map(assoc(k)(v))

f('author')('John Doe')(data) // ?

也可使用 assoc() 新增 property 產生 map() 的 callback。

assoc003

compose()

import { map, assoc, compose } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = k => v => compose(
  map,
  assoc(k)
)(v)

f('author')('John Doe')(data) // ?

由於 assoc()map() 的 callback,因此可用 compose()assoc()map() 組合,最後利用 IIFE 傳入 v 求得結果。

assoc004

Point-free

import { map, assoc, compose } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = k => compose(
  map,
  assoc(k)
)

f('author')('John Doe')(data) // ?

由於 v 為最後一個 argument,剛好可 point-free 消除,但可惜還留下 k

assoc005

useWith()

import { map, assoc, compose, identity, useWith, curry } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = curry(compose(
  map,
  useWith(assoc, [identity, identity])
))


f('author')('John Doe')(data) // ?

也有另外一種 point-free 方法,由於 assoc() 要接受 kv 兩個 argument,可使用 useWith()identity() 墊出兩個 argument,如此則完全 point-free。

useWith() 雖然支援 curried function,但使用 compose() 時會當成 unary function 組合,因此最後結果不是 curried function,需再使用 curry() 使其 currying。

assoc006

Conclusion

  • Ramda 會使用 assoc() 新增 property,一般而言重構成 compose() 就很不錯,若能力許可可用 useWith() 進一步 point-free

Reference

Ramda, map()
Ramda, assoc()
Ramda, compose()
Ramda, useWith()