就像 Iven Marquardt 所说,您的方法很好并且符合函数式编程原则,但可能会重复。我建议改用镜头。
什么是镜头?
What are lenses used/useful for? 问题中对镜头有很好的解释。尽管问题是针对 Haskell 的,但答案和解释仍然主要适用于 JS。
本质上,镜头是一种获取、设置和修改数据结构部分(例如对象的属性或数组元素)的方法。这会将{...state, c: map(extractProperty)})} 抽象为modify(cLens, map(extractProperty), state) 之类的东西。
一个简单的实现
虽然我建议使用库来简化操作,但了解镜头工作原理的基础知识会有所帮助。
const get = lens => s => lens.get(s)
const modify = lens => f => lens.modify(f)
const set = lens => a => lens.modify(() => a)
// You can also compose lenses:
const composeLens = (sa, ab) => ({
get: s => ab.get(sa.get(s)),
modify: f => sa.modify(ab.modify(f))
})
const propLens = prop => ({
get: ({[prop]: a}) => a,
modify: f => ({[prop]: a, ...rest}) => ({...rest, [prop]: f(a)})
})
const idxLens = i => ({
get: arr => arr[i],
modify: f => arr => [...arr.slice(0, i), f(arr[i]), ...arr.slice(i + 1)]
})
const cLens = propLens('c')
const headLens = idxLens(0)
const headOfC = composeLens(cLens, headLens)
const state = {b: 0, c: [1, 2, 3]}
console.log(get(headOfC)(state)) // 1
console.log(set(headOfC)(4)(state)) // {b: 0, c: [4, 2, 3]}
console.log(modify(headOfC)(x => 5 * x)(state)) // {b: 0, c: [5, 2, 3]}
// your example:
modify(cLens)(map(extractProperty))(A)
// or alternatively
cLens.modify(map(extractProperty))(A)
使用库
虽然上述实现有效,但镜头的其他实现更通用,并且更方便,例如棱镜(可以返回可选值)和遍历(可以返回值的应用函子)。
正如 Iven Marquardt 所建议的,Ramda 是一个很好的包含镜头的函数式编程库:
const cLens = R.lensProp('c')
const headLens = R.lensIndex(0)
const headOfC = R.compose(cLens, headLens)
const state = {b: 0, c: [1, 2, 3]}
console.log(R.view(headOfC, state)) // 1
console.log(R.set(headOfC, 4, state)) // {b: 0, c: [4, 2, 3]}
console.log(R.over(headOfC, x => 5 * x, state)) // {b: 0, c: [5, 2, 3]}
<script src="https://cdn.jsdelivr.net/npm/ramda@0.27.0/dist/ramda.min.js"></script>
就我个人而言,我更喜欢使用 TypeScript 并且更喜欢 monocle-ts(您仍然可以在纯 JavaScript 中使用它),因为它具有更好的 TypeScript 支持并与 fp-ts 很好地集成:
import {Lens} from 'monocle-ts'
import {indexReadonlyArray} from 'monocle-ts/lib/Index/ReadonlyArray'
interface State {
readonly b: number
readonly c: readonly number[]
}
const cLens = Lens.fromProp<State>()('c')
const headLens = indexReadonlyArray<number>().index(0)
const headOfC = cLens.composeOptional(headLens)
const state = {b: 0, c: [1, 2, 3]}
console.log(headOfC.getOption(state)) // {_tag: 'Some', value: 1}
console.log(headOfC.set(4)(state)) // {b: 0, c: [4, 2, 3]}
console.log(headOfC.modify(x => x * 5)(state)) // {b: 0, c: [5, 2, 3]}