Como faço para replace loops while com uma alternativa de functional programming sem otimização da chamada final?

Eu estou experimentando com um estilo mais funcional no meu JavaScript; Portanto, substituí lo for loops com funções de utilitário, como mapear e reduzir. No entanto, eu não encontrei um substituto funcional for loops while, já que a otimização da chamada final geralmente não está disponível para JavaScript. (Pelo que entendi, o ES6 impede que as chamadas finais transbordem da pilha, mas não otimiza seu desempenho.)

Eu explico o que tentei abaixo, mas o TLDR é: Se eu não tiver a otimização da chamada final, qual é a maneira funcional de implementar loops while?

O que eu tentei:

Criando uma function de utilitário “while”:

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

Como a otimização de chamada de cauda não está disponível, eu poderia rewrite isso como:

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

No entanto, neste momento parece que eu fiz o meu código mais complicado / confuso para qualquer outra pessoa que usa, uma vez que eu tenho que arrastar em torno de uma function de utilitário personalizado. A única vantagem prática que vejo é que me força a tornar o loop puro; mas parece que seria mais simples usar apenas um loop regular e garantir que eu mantenha tudo puro.

Eu também tentei descobrir uma maneira de criar uma function de gerador que imita os efeitos de recursion / loop e, em seguida, iterar sobre ele usando uma function de utilitário como localizar ou reduzir. No entanto, ainda não descobri uma maneira legível de fazer isso.

Finalmente, replace loops por funções de utilidade torna mais aparente o que estou tentando realizar (por exemplo, fazer uma coisa para cada elemento, verificar se cada elemento passa por um teste etc.). No entanto, parece-me que um loop while já expressa o que estou tentando realizar (por exemplo, iterar até encontrarmos um número primo, iterar até que a resposta seja suficientemente otimizada etc.).

Então, depois de tudo isso, minha pergunta geral é: se eu preciso de um loop while, estou programando em um estilo funcional e não tenho access à otimização de chamada final, então qual é a melhor estratégia.

Um exemplo em JavaScript

Aqui está um exemplo usando JavaScript. Atualmente, a maioria dos navegadores não oferece suporte à otimização de chamada final e, portanto, o seguinte snippet falhará

 const repeat = n => f => x => n === 0 ? x : repeat (n - 1) (f) (f(x)) console.log(repeat(1e3) (x => x + 1) (0)) // 1000 console.log(repeat(1e5) (x => x + 1) (0)) // Error: Uncaught RangeError: Maximum call stack size exceeded 

Programar no sentido do paradigma funcional significa que somos guiados por tipos para expressar nossos algoritmos.

Para transformar uma function recursiva de cauda em uma versão segura de pilha, temos que considerar dois casos:

  • caso base
  • caso recursivo

Temos que fazer uma escolha e isso vai bem com os sindicatos marcados. No entanto, o Javascript não tem esse tipo de dados, portanto, temos que criar um ou voltar para as codificações do Object .

Objeto codificado

 // 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(); 

Veja também desdobrar qual (de documentação da Ramda)

Cria uma lista a partir de um valor inicial. Aceita uma function iteradora, que retorna falso para parar a iteração ou uma matriz de comprimento 2 contendo o valor a ser adicionado à lista resultante e a semente a ser usada na próxima chamada para a function iteradora.

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