在函数式编程中操纵状态
Manipulating state in functional programming
我刚开始学习函数式编程,我想知道在下面的情况下我应该怎么做
- 假设我们有一些状态A
具有以下结构
let A = {
b: {
...usefulldata...
},
c: [
{ ...entity1... },
{ ...entity2... },
{ ...entity2... },
]
}
我们有一些函数可以从实体中提取一些 属性 我们称之为 extractProperty
我们的目标是编写函数 handleState
( A -> object with mapped array in 属性 c)
所以它接收状态A
(从第一步)
然后它应该将所有实体映射到具有函数 extractProperty
的属性
最后它应该 return 在 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)}),
)
但这在函数式编程中是否可行,或者有其他方法可以解决这个问题?
正如 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)
使用库
虽然上述实现有效,但还有其他更通用的镜头实现,可以促进棱镜(可以 return 一个可选值)和遍历(可以 return 一个应用函子值)。
正如 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]}
我刚开始学习函数式编程,我想知道在下面的情况下我应该怎么做
- 假设我们有一些状态A 具有以下结构
let A = {
b: {
...usefulldata...
},
c: [
{ ...entity1... },
{ ...entity2... },
{ ...entity2... },
]
}
我们有一些函数可以从实体中提取一些 属性 我们称之为
extractProperty
我们的目标是编写函数
handleState
( A -> object with mapped array in 属性 c)所以它接收状态
A
(从第一步)然后它应该将所有实体映射到具有函数
的属性extractProperty
最后它应该 return 在 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)}),
)
但这在函数式编程中是否可行,或者有其他方法可以解决这个问题?
正如 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)
使用库
虽然上述实现有效,但还有其他更通用的镜头实现,可以促进棱镜(可以 return 一个可选值)和遍历(可以 return 一个应用函子值)。
正如 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]}