函数式编程:当需要改变输入时如何将不纯函数转换为纯函数
Functional Programming: How to convert an impure function to a pure function when the input needs to be mutated
如何创建一个纯函数来更新已在另一个函数中初始化的对象
类似于:
parentFunction = (inputs: object[], condtionList: string[]) => {
const newObject = {f1: val1[], f2: val2[], f3: val3[]...}
inputs.forEach(input => {
if(condition1){
updateNewObject(condtion1, newObject, input, conditionList)
}
.
.
.
}
return newObject
}
下面的函数是不纯的,因为它正在更新 newObject(改变输入)我怎样才能将它转换为纯函数?
updateNewObject(condition, newObject, input, conditionList) {
const i = conditionList.indexOf(input.condition)
if(i === 0){
newObject.f1.push(input)
}
else if(i === 1) {
newObject.f2.push(input)
}
.
.
.
}
以上没有return值。它将 newObject 作为输入,并根据某些条件将值推送到 newObject 的属性。有没有办法让上面的功能变得纯粹?还是我必须重新考虑如何更新 newObject?
只需 copy/map 数组即可获得一个新数组。不要变异同一个。
通常,您会创建一个新对象,其中包含一个包含新条目的新数组:
if(i === 0){
// v−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−v−− creates new object
newObject = {...newObject, f1: [...newObject.f1, input]}
// ^ ^−−−−−−−−−−−−−−−−−−−−−−^−−− creates new array
}
else if(i === 1) {
// v−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−v−− creates new object
newObject = {...newObject, f2: [...newObject.f2, input]}
// ^ ^−−−−−−−−−−−−−−−−−−−−−−^−−− creates new array
}
然后在parentFunction
:
newObject = updateNewObject(condtion1, newObject, input, conditionList)
// ^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−− updates the value being returned
或者更新可以是:
const name = i === 0 ? "f1" : (i === 1 ? "f2" : ""));
if (name) {
newObject = {...newObject, [name]: [...newObject[name], input]}
}
...尽管嵌套条件语句有点无聊。 :-)
如果您希望 updateNewObject
是纯的,让它创建一个克隆原始对象的新对象,对其进行变异,然后 return 新对象。
updateNewObject(condition, oldObject, input, conditionList) {
const newObject = {...oldObject};
const i = conditionList.indexOf(input.condition)
if(i === 0){
newObject.f1 = [...newObject.f1, input];
}
else if(i === 1) {
newObject.f2 = [...newObject.f2, input];
}
.
.
.
return newObject;
}
注意 newObject.f1 = [...newObject.f1, input];
如何创建一个新数组 - 这确保我们不仅不会直接改变对象,而且不会改变它的任何字段(数组),而是创建新的。
然后调整 parentFunction
以便它使用每个 returned updateNewObject
调用的值:
parentFunction = (inputs: object[], condtionList: string[]) => {
let newObject = {f1: val1[], f2: val2[], f3: val3[]...}
inputs.forEach(input => {
if(condition1){
newObject = updateNewObject(condtion1, newObject, input, conditionList)
}
.
.
.
}
return newObject
}
函数式编程不仅与纯度有关,还与可重用性和关注点分离有关。编写一个大而复杂的函数很困难,测试和维护它更难。遵循功能性原则将帮助我们避免疼痛和不适。
让我们从隔离我们关心的行为开始。我们确定函数 push
、update
和 pushKey
-
const identity = x =>
x
const push = (a = [], value) =>
a.concat([ value ])
const update = (o = {}, key = "", t = identity) =>
({ ...o, [key]: t(o[key]) })
const pushKey = (o = {}, key = "", value) =>
update(o, key, a => push(a, value))
这使您可以轻松地执行基本的不可变转换 -
const d1 = { a: [1], b: [] }
const d2 = pushKey(d1, "a", 2)
const d3 = pushKey(d2, "b", 3)
const d4 = pushKey(d3, "c", 4)
console.log(d1) // { a: [1], b: [] }
console.log(d2) // { a: [1, 2], b: [] }
console.log(d3) // { a: [1, 2], b: [3] }
console.log(d4) // { a: [1, 2], b: [3], c: [4] }
将下面的代码片段展开为 运行 您自己的浏览器中的程序 -
const identity = x =>
x
const push = (a = [], value) =>
a.concat([ value ])
const update = (o = {}, key = "", t = identity) =>
({ ...o, [key]: t(o[key]) })
const pushKey = (o = {}, key = "", value) =>
update(o, key, a => push(a, value))
const d1 = { a: [1], b: [] }
const d2 = pushKey(d1, "a", 2)
const d3 = pushKey(d2, "b", 3)
const d4 = pushKey(d3, "c", 4)
console.log(JSON.stringify(d1)) // { a: [1], b: [] }
console.log(JSON.stringify(d2)) // { a: [1, 2], b: [] }
console.log(JSON.stringify(d3)) // { a: [1, 2], b: [3] }
console.log(JSON.stringify(d4)) // { a: [1, 2], b: [3], c: [4] }
这允许您将复杂的条件逻辑分离到它自己的函数中 -
const updateByCondition = (o = {}, conditions = [], ...) =>
{ if (...)
return pushKey(o, "foo", someValue)
else if (...)
return pushKey(o, "bar", someValue)
else
return pushKey(o, "default", someValue)
}
这种方法的优点很多。 push
、update
和 pushKey
都非常容易编写、测试和维护,而且它们很容易在我们程序的其他部分重用。编写 updateByCondition
更容易,因为我们有更好的基本构建块。由于您尝试编码的任何复杂性,它仍然很难测试,但是由于关注点分离,它更容易维护。
如何创建一个纯函数来更新已在另一个函数中初始化的对象 类似于:
parentFunction = (inputs: object[], condtionList: string[]) => {
const newObject = {f1: val1[], f2: val2[], f3: val3[]...}
inputs.forEach(input => {
if(condition1){
updateNewObject(condtion1, newObject, input, conditionList)
}
.
.
.
}
return newObject
}
下面的函数是不纯的,因为它正在更新 newObject(改变输入)我怎样才能将它转换为纯函数?
updateNewObject(condition, newObject, input, conditionList) {
const i = conditionList.indexOf(input.condition)
if(i === 0){
newObject.f1.push(input)
}
else if(i === 1) {
newObject.f2.push(input)
}
.
.
.
}
以上没有return值。它将 newObject 作为输入,并根据某些条件将值推送到 newObject 的属性。有没有办法让上面的功能变得纯粹?还是我必须重新考虑如何更新 newObject?
只需 copy/map 数组即可获得一个新数组。不要变异同一个。
通常,您会创建一个新对象,其中包含一个包含新条目的新数组:
if(i === 0){
// v−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−v−− creates new object
newObject = {...newObject, f1: [...newObject.f1, input]}
// ^ ^−−−−−−−−−−−−−−−−−−−−−−^−−− creates new array
}
else if(i === 1) {
// v−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−v−− creates new object
newObject = {...newObject, f2: [...newObject.f2, input]}
// ^ ^−−−−−−−−−−−−−−−−−−−−−−^−−− creates new array
}
然后在parentFunction
:
newObject = updateNewObject(condtion1, newObject, input, conditionList)
// ^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−− updates the value being returned
或者更新可以是:
const name = i === 0 ? "f1" : (i === 1 ? "f2" : ""));
if (name) {
newObject = {...newObject, [name]: [...newObject[name], input]}
}
...尽管嵌套条件语句有点无聊。 :-)
如果您希望 updateNewObject
是纯的,让它创建一个克隆原始对象的新对象,对其进行变异,然后 return 新对象。
updateNewObject(condition, oldObject, input, conditionList) {
const newObject = {...oldObject};
const i = conditionList.indexOf(input.condition)
if(i === 0){
newObject.f1 = [...newObject.f1, input];
}
else if(i === 1) {
newObject.f2 = [...newObject.f2, input];
}
.
.
.
return newObject;
}
注意 newObject.f1 = [...newObject.f1, input];
如何创建一个新数组 - 这确保我们不仅不会直接改变对象,而且不会改变它的任何字段(数组),而是创建新的。
然后调整 parentFunction
以便它使用每个 returned updateNewObject
调用的值:
parentFunction = (inputs: object[], condtionList: string[]) => {
let newObject = {f1: val1[], f2: val2[], f3: val3[]...}
inputs.forEach(input => {
if(condition1){
newObject = updateNewObject(condtion1, newObject, input, conditionList)
}
.
.
.
}
return newObject
}
函数式编程不仅与纯度有关,还与可重用性和关注点分离有关。编写一个大而复杂的函数很困难,测试和维护它更难。遵循功能性原则将帮助我们避免疼痛和不适。
让我们从隔离我们关心的行为开始。我们确定函数 push
、update
和 pushKey
-
const identity = x =>
x
const push = (a = [], value) =>
a.concat([ value ])
const update = (o = {}, key = "", t = identity) =>
({ ...o, [key]: t(o[key]) })
const pushKey = (o = {}, key = "", value) =>
update(o, key, a => push(a, value))
这使您可以轻松地执行基本的不可变转换 -
const d1 = { a: [1], b: [] }
const d2 = pushKey(d1, "a", 2)
const d3 = pushKey(d2, "b", 3)
const d4 = pushKey(d3, "c", 4)
console.log(d1) // { a: [1], b: [] }
console.log(d2) // { a: [1, 2], b: [] }
console.log(d3) // { a: [1, 2], b: [3] }
console.log(d4) // { a: [1, 2], b: [3], c: [4] }
将下面的代码片段展开为 运行 您自己的浏览器中的程序 -
const identity = x =>
x
const push = (a = [], value) =>
a.concat([ value ])
const update = (o = {}, key = "", t = identity) =>
({ ...o, [key]: t(o[key]) })
const pushKey = (o = {}, key = "", value) =>
update(o, key, a => push(a, value))
const d1 = { a: [1], b: [] }
const d2 = pushKey(d1, "a", 2)
const d3 = pushKey(d2, "b", 3)
const d4 = pushKey(d3, "c", 4)
console.log(JSON.stringify(d1)) // { a: [1], b: [] }
console.log(JSON.stringify(d2)) // { a: [1, 2], b: [] }
console.log(JSON.stringify(d3)) // { a: [1, 2], b: [3] }
console.log(JSON.stringify(d4)) // { a: [1, 2], b: [3], c: [4] }
这允许您将复杂的条件逻辑分离到它自己的函数中 -
const updateByCondition = (o = {}, conditions = [], ...) =>
{ if (...)
return pushKey(o, "foo", someValue)
else if (...)
return pushKey(o, "bar", someValue)
else
return pushKey(o, "default", someValue)
}
这种方法的优点很多。 push
、update
和 pushKey
都非常容易编写、测试和维护,而且它们很容易在我们程序的其他部分重用。编写 updateByCondition
更容易,因为我们有更好的基本构建块。由于您尝试编码的任何复杂性,它仍然很难测试,但是由于关注点分离,它更容易维护。