【问题标题】:Manipulating state in functional programming在函数式编程中操纵状态
【发布时间】:2021-10-22 17:08:32
【问题描述】:

刚开始学习函数式编程,想知道下面这种情况该怎么办

  1. 假设我们有一些状态 A 具有以下结构
let A =  {
   b: {
     ...usefulldata...
   },
   c: [
     { ...entity1... },
     { ...entity2... },
     { ...entity2... },
   ]

}
  1. 我们有一些函数可以从实体中提取一些属性,我们称之为extractProperty

  2. 我们的目标是编写函数handleState(A -> 属性c中具有映射数组的对象)

    所以它接收状态A(从第一步开始)

    那么它应该将所有实体映射到具有函数extractProperty的属性

    最后它应该像这样在 c 属性中返回带有新数组的新状态

{
   b: {
     ...usefulldata...
   },
   c: [
     propertyOfentity1,
     propertyOfentity2,
     propertyOfentity3,
   ]

}

所以我试着写这样的东西

handleState = pipe(
   extractC,
   map(extractProperty),
)

但问题是我最终只得到c 数组,所以 可能的解决方案是

handleState = pipe(
   extractC,
   (state) => ({
       ...state, //Extract old state to new state
       c: map(extractProperty)}),
)

但这在函数式编程中可以吗,还是有其他方法可以解决这个问题?

【问题讨论】:

  • 您的方法很好,但它也是临时的。使用功能性镜头,例如Ramda,以获得更通用的方法。

标签: javascript functional-programming


【解决方案1】:

就像 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]}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-16
    • 2010-09-26
    • 1970-01-01
    • 2021-03-22
    • 2012-05-10
    • 2021-01-26
    相关资源
    最近更新 更多