Функциональное программирование: как преобразовать нечистую функцию в чистую функцию, когда необходимо изменить вход - PullRequest
0 голосов
/ 06 апреля 2020

Как я могу создать чистую функцию, которая обновляет объект, который был инициализирован в другой функции, например:

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

Выше не имеет возвращаемого значения. Он принимает newObject в качестве входных данных и на основе некоторых условных выражений передает значения в свойства newObject. Есть ли способ сделать вышеуказанную функцию чистой? или мне нужно переосмыслить, как я обновляю newObject?

Ответы [ 4 ]

3 голосов
/ 06 апреля 2020

Функциональное программирование - это не только чистота, но и возможность повторного использования и разделения задач. Трудно написать большую сложную функцию, и еще сложнее протестировать и поддерживать ее. Следование функциональным принципам поможет нам избежать боли и дискомфорта.

Давайте начнем с выделения поведения, которое нам небезразлично. Мы идентифицируем функции 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))

Это позволяет легко выполнять базовые c неизменные преобразования -

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

Это позволяет вам разделить ваш сложный условный лог c на его собственную функцию -

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 было намного проще, потому что у нас были лучшие базовые блоки c. Это все еще трудно проверить из-за сложности, которую вы пытаетесь закодировать, однако его гораздо проще поддерживать из-за разделения проблем.

2 голосов
/ 06 апреля 2020

Обычно вы создаете новый объект с новым массивом с новой записью в нем:

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

... хотя вложенное условное выражение немного ме. : -)

1 голос
/ 06 апреля 2020

Если вы хотите, чтобы updateNewObject был чистым, он должен создать новый объект, который клонирует оригинал, изменить его, а затем вернуть новый объект.

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, чтобы он использует значение каждого возвращенного 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
}
0 голосов
/ 06 апреля 2020

Просто скопируйте / отобразите массив, чтобы получить новый. Не изменяйте то же самое.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...