web-dev-qa-db-fra.com

Comment remplacer des boucles While par une alternative de programmation fonctionnelle sans optimisation d'appel final?

J'expérimente un style plus fonctionnel dans mon JavaScript; par conséquent, j'ai remplacé les boucles for par des fonctions utilitaires telles que map et réduire. Cependant, je n'ai pas trouvé de remplaçant fonctionnel pour les boucles while puisque l'optimisation des appels de fin n'est généralement pas disponible pour JavaScript. (D'après ce que je comprends, ES6 empêche les appels de queue de déborder de la pile, mais n'optimise pas leurs performances.)

J'explique ce que j'ai essayé ci-dessous, mais le TLDR est le suivant: Si je ne dispose pas de l'optimisation d'appel final, quelle est la manière fonctionnelle de mettre en œuvre les boucles while?

Ce que j'ai essayé

Création d'une fonction utilitaire "while":

function while(func, test, data) {
  const newData = func(data);
  if(test(newData)) {
    return newData;
  } else {
    return while(func, test, newData);
  }
}

Puisque l'optimisation des appels de queue n'est pas disponible, je pourrais récrire ceci comme suit:

function while(func, test, data) {
  let newData = *copy the data somehow*
  while(test(newData)) {
    newData = func(newData);
  }
  return newData;
}

Cependant, à ce stade, j'ai l'impression que j'ai rendu mon code plus compliqué/confus pour quiconque l'utilise, car je dois trimballer une fonction utilitaire personnalisée. Le seul avantage pratique que je vois est que cela me force à purifier la boucle; mais il semble qu'il serait plus simple d'utiliser une boucle while régulière et de m'assurer que tout reste pur.

J'ai également essayé de trouver un moyen de créer une fonction génératrice qui imite les effets de la récursivité/mise en boucle, puis de la parcourir au moyen d'une fonction utilitaire telle que rechercher ou réduire. Cependant, je n'ai pas encore trouvé de moyen lisible de le faire.

Enfin, le remplacement des boucles for par des fonctions utilitaires rend plus évident ce que j'essaie d'accomplir (par exemple, faire une chose sur chaque élément, vérifier si chaque élément réussit un test, etc.). Cependant, il me semble qu'une boucle while exprime déjà ce que j'essaie d'accomplir (par exemple, itérer jusqu'à trouver un nombre premier, itérer jusqu'à ce que la réponse soit suffisamment optimisée, etc.).

Donc, après tout cela, ma question générale est la suivante: si j’ai besoin d’une boucle while, je programme dans un style fonctionnel et je n’ai pas accès à l’optimisation des appels en attente, alors quelle est la meilleure stratégie.

29
David Moneysmith

La programmation au sens du paradigme fonctionnel signifie que nous sommes guidés par des types pour exprimer nos algorithmes.

Pour transformer une fonction récursive en une version sécurisée, nous devons considérer deux cas:

  • cas de base
  • cas récursif

Nous devons faire un choix et cela va bien avec les syndicats étiquetés. Cependant, Javascript n’a pas un tel type de données, nous devons donc en créer un ou revenir aux encodages Object.

Objet encodé

// simulate a tagged union with two Object types

const Loop = x =>
  ({value: x, done: false});
  
const Done = x =>
  ({value: x, done: true});

// trampoline

const tailRec = f => (...args) => {
  let step = Loop(args);

  do {
    step = f(Loop, Done, step.value);
  } while (!step.done);

  return step.value;
};

// stack-safe function

const repeat = n => f => x =>
  tailRec((Loop, Done, [m, y]) => m === 0
    ? Done(y)
    : Loop([m - 1, f(y)])) (n, x);

// run...

const inc = n =>
  n + 1;

console.time();
console.log(repeat(1e6) (inc) (0));
console.timeEnd();

Fonction encodée

Alternativement, nous pouvons créer une véritable union étiquetée avec un encodage de fonction. Notre style est maintenant beaucoup plus proche des langages fonctionnels matures:

// type/data constructor

const Type = Tcons => (tag, Dcons) => {
  const t = new Tcons();
  t.run = cases => Dcons(cases);
  t.tag = tag;
  return t;
};

// tagged union specific for the case

const Step = Type(function Step() {});

const Done = x =>
  Step("Done", cases => cases.Done(x));
  
const Loop = args =>
  Step("Loop", cases => cases.Loop(args));

// trampoline

const tailRec = f => (...args) => {
  let step = Loop(args);

  do {
    step = f(step);
  } while (step.tag === "Loop");

  return step.run({Done: id});
};

// stack-safe function

const repeat = n => f => x => 
  tailRec(step => step.run({
    Loop: ([m, y]) => m === 0 ? Done(y) : Loop([m - 1, f(y)]),
    Done: y => Done(y)
  })) (n, x);

// run...

const inc = n => n + 1;
const id = x => x;

console.log(repeat(1e6) (inc) (0));

2
user6445533

Voir aussi unfold which (de Ramda docs)

Construit une liste à partir d'une valeur de départ. Accepte une fonction itérateur, qui renvoie soit false pour arrêter l'itération, soit un tableau de longueur 2 contenant la valeur à ajouter à la liste résultante et la graine à être utilisé lors du prochain appel à la fonction itérateur.

var r = n => f => x => x > n ? false : [x, f(x)];
var repeatUntilGreaterThan = n => f => R.unfold(r(n)(f), 1);
console.log(repeatUntilGreaterThan(10)(x => x + 1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script>

0
gpilotino

J'ai beaucoup réfléchi à cette question. Récemment, j'ai eu besoin d'une boucle while fonctionnelle. 

Il me semble que la seule chose que cette question veut vraiment, c'est un moyen de faire une boucle en boucle. Il y a IS un moyen de le faire en utilisant une fermeture.

"some string "+(a=>{
   while(comparison){
      // run code
   }
   return result;
})(somearray)+" some more"

Sinon, si ce que vous voulez est quelque chose qui chaîne un tableau, vous pouvez utiliser la méthode de réduction. 

somearray.reduce((r,o,i,a)=>{
   while(comparison){
      // run code
   }
   a.splice(1); // This would ensure only one call.
   return result;
},[])+" some more"

Rien de tout cela ne transforme réellement notre boucle while en une fonction. Mais cela nous permet d'utiliser une boucle en ligne. Et je voulais juste partager cela avec tous ceux qui pourraient aider.

0
bronkula