简体   繁体   English

函数式编程:当输入需要突变时,如何将不纯的 function 转换为纯 function

[英]Functional Programming: How to convert an impure function to a pure function when the input needs to be mutated

How can I create a pure function that updates an object that's been initialized in another function something like:如何创建一个纯 function 来更新已在另一个 function 中初始化的 object,例如:

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
}

The below function is impure as it's updating the newObject (mutating the input) how can I convert it to a pure function?下面的 function 是不纯的,因为它正在更新 newObject(改变输入)如何将其转换为纯 function?

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)
  }
  .
  .
  .
}

The above has no return value.以上没有返回值。 It takes the newObject as input and based on some conditionals pushes values to the properties of the newObject.它将 newObject 作为输入,并根据一些条件将值推送到 newObject 的属性。 Is there anyway to make the above function pure?有没有办法让上面的 function 纯? or do I have to rethink how I am updating newObject?还是我必须重新考虑如何更新 newObject?

Functional programming is not only about purity, it's also about reusability and separation of concerns.函数式编程不仅关乎纯度,还关乎可重用性和关注点分离。 It's difficult to write a big complex function, and even harder to test and maintain it.写一个大复杂的function很难,更难测试和维护。 Following functional principles will help us avoid pain and discomfort.遵循功能原则将帮助我们避免疼痛和不适。

Let's start by isolating the behaviours we care about.让我们从隔离我们关心的行为开始。 We identify functions push , update , and pushKey -我们识别函数pushupdatepushKey -

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))

This allows you to perform basic immutable transformations easily -这使您可以轻松地执行基本的不可变转换 -

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

Expand the snippet below to run the program in your own browser -展开下面的代码片段以在您自己的浏览器中运行该程序 -

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

This allows you to separate your complex conditional logic into its own function -这使您可以将复杂的条件逻辑分离成自己的 function -

const updateByCondition = (o = {}, conditions = [], ...) =>
{ if (...)
    return pushKey(o, "foo", someValue)
  else if (...)
    return pushKey(o, "bar", someValue) 
  else
    return pushKey(o, "default", someValue) 
}

The advantages to this approach are numerous.这种方法的优点很多。 push , update , and pushKey are all very easy to write, test, and maintain, and they're easy to reuse in other parts of our program. pushupdatepushKey都非常容易编写、测试和维护,并且很容易在我们程序的其他部分重用。 Writing updateByCondition was much easier because we had better basic building blocks.编写updateByCondition要容易得多,因为我们有更好的基本构建块。 It's still difficult to test due to whatever complexity you are trying to encode, however it is much easier to maintain due to separation of concerns.由于您尝试编码的任何复杂性,它仍然难以测试,但是由于关注点分离,它更容易维护。

Classically, you'd create a new object with a new array with the new entry in it:经典地,您将创建一个新的 object ,其中包含一个新数组,其中包含新条目:

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
}

Then in parentFunction :然后在parentFunction

    newObject = updateNewObject(condtion1, newObject, input, conditionList)
//  ^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−− updates the value being returned

Or the update could be:或者更新可能是:

const name = i === 0 ? "f1" : (i === 1 ? "f2" : ""));
if (name) {
  newObject = {...newObject, [name]: [...newObject[name], input]}
}

...though the nested conditional is a bit meh. ...虽然嵌套条件有点meh。 :-) :-)

If you want updateNewObject to be pure, have it create a new object that clones the original, mutate that, and then return the new object.如果您希望updateNewObject是纯的,让它创建一个新的 object 克隆原始对象,对其进行变异,然后返回新的 object。

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;
}

Note how newObject.f1 = [...newObject.f1, input];注意newObject.f1 = [...newObject.f1, input]; creates a new array - this ensures that we not only don't mutate the object directly, but we don't mutate any of its fields (arrays) and instead create new ones.创建一个新数组——这确保我们不仅不会直接改变 object,而且不会改变它的任何字段(数组),而是创建新的字段。

Then tweak parentFunction so that it uses the value of each returned updateNewObject call:然后调整parentFunction以便它使用每个返回的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
}

Just copy/map the array to get a new one.只需复制/映射数组即可获得一个新数组。 Don't mutate the same one.不要突变同一个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM