0
votes

Programmation fonctionnelle: Comment convertir une fonction impure en une fonction pure lorsque l'entrée doit être mutée

Comment puis-je créer une fonction pure qui met à jour un objet qui a été initialisé dans une autre fonction quelque chose comme:

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

La fonction ci-dessous est impure car elle met à jour le newObject (mutation de l'entrée) comment puis-je le convertir en une fonction pure?

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
}


2 commentaires

Les types JS Object / Array ne dépendent pas de la mutabilité par conception! Ne modifiez simplement pas l'entrée. Si vous êtes préoccupé par l'efficacité, utilisez des types de données immuables.


Êtes-vous en train de dire que vous ne pouvez pas modifier parentFunction ?


4 Réponses :


-1
votes

Copiez / mappez simplement le tableau pour en obtenir un nouveau. Ne mute pas le même.


2 commentaires

Je ne comprends pas le vote négatif sur cette réponse. C'est correct. C'est vraiment laconique, mais c'est correct.


@ T.J.Crowder ça arrive: D



1
votes

Classiquement, vous créeriez un nouvel objet avec un nouveau tableau contenant la nouvelle entrée:

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

Puis dans parentFunction :

XXX


Ou la mise à jour pourrait être:

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

... bien que le conditionnel imbriqué soit un peu meh. :-)


1 commentaires

name = ["f1", "f2"] [i] ?? "" :-)



1
votes

Si vous voulez que updateNewObject soit pur, demandez-lui de créer un nouvel objet qui clone l'original, de le muter, puis de renvoyer le nouvel objet.

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
}

Notez comment newObject.f1 = [... newObject.f1, input]; crée un nouveau tableau - cela garantit non seulement que nous ne mutons pas l'objet directement, mais que nous ne mutons aucun de ses champs (tableaux) et créez-en de nouveaux.

Puis ajustez parentFunction afin qu'il utilise la valeur de chaque appel updateNewObject retourné: p>

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


2 commentaires

@ T.J.Crowder Non - vérifiez la ligne const newObject = {... oldObject};


Ah, oui, désolé. :-) Je ne vois aucune raison de copier l'objet lorsque vous ne le mettez pas à jour (quand i n'est ni 0 ni 1 ).



3
votes

La programmation fonctionnelle n'est pas seulement une question de pureté, c'est aussi une question de réutilisabilité et de séparation des préoccupations. Il est difficile d'écrire une grande fonction complexe, et encore plus difficile de la tester et de la maintenir. Suivre les principes fonctionnels nous aidera à éviter la douleur et l'inconfort.

Commençons par isoler les comportements qui nous tiennent à cœur. Nous identifions les fonctions push , update et pushKey -

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

Cela vous permet de effectuez facilement des transformations immuables de base -

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

Développez l'extrait ci-dessous pour exécuter le programme dans votre propre navigateur -

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

Cela vous permet de séparer votre logique conditionnelle complexe en sa propre fonction -

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

Les avantages de cette approche sont nombreux. push , update et pushKey sont tous très faciles à écrire, tester et maintenir, et ils sont faciles à réutiliser dans d'autres parties de notre programme. L'écriture de updateByCondition était beaucoup plus facile car nous avions de meilleurs blocs de construction de base. Il est toujours difficile de tester en raison de la complexité que vous essayez d'encoder, mais il est beaucoup plus facile à maintenir en raison de la séparation des préoccupations.


2 commentaires

Peut-être montrer comment vous appliquerez ceux-ci à la mise à jour de l'objet spécifique + spécifique de l'OP.


L'extrait de code aide, mais l'utilisation des noms de l'OP faciliterait considérablement la lisibilité.