As funções do gerador são válidas na programação funcional?


17

As perguntas são:

  • Os geradores quebram o paradigma de programação funcional? Por que ou por que não?
  • Se sim, os geradores podem ser usados ​​na programação funcional e como?

Considere o seguinte:

function * downCounter(maxValue) {
  yield maxValue;
  yield * downCounter(maxValue > 0 ? maxValue - 1 : 0);
}

let counter = downCounter(26);
counter.next().value; // 26
counter.next().value; // 25
// ...etc

O downCountermétodo parece sem estado. Além disso, chamar downCountercom a mesma entrada sempre resultará na mesma saída. No entanto, ao mesmo tempo, a chamada next()não produz resultados consistentes.

Não tenho certeza se os geradores quebram ou não o paradigma de programação funcional, porque neste exemplo counteré um objeto gerador e, portanto, a chamada next()produziria os mesmos resultados que outro objeto gerador criado exatamente com o mesmo maxValue.

Além disso, chamar someCollection[3]uma matriz sempre retornaria o quarto elemento. Da mesma forma, chamar next()quatro vezes em um objeto gerador também sempre retornaria o quarto elemento.

Para mais contexto, essas questões foram levantadas durante o trabalho em um kata de programação . A pessoa que respondeu à pergunta levantou a questão de saber se os geradores poderiam ou não ser usados ​​na programação funcional e se eles mantêm ou não o estado.


2
Todo programa possui estado. A verdadeira questão é se ele se qualifica como estado funcional , que eu interpreto como "estado imutável", estado que não muda depois de atribuído. Afirmo que a única maneira de fazer um gerador retornar algo diferente em cada chamada é se o estado mutável estiver de alguma forma envolvido.
Robert Harvey

Respostas:


14

As funções do gerador não são particularmente especiais. Podemos implementar um mecanismo semelhante, reescrevendo a função do gerador em um estilo baseado em retorno de chamada:

function downCounter(maxValue) {
  return {
    "value": maxValue,
    "next": function () {
      return downCounter(maxValue > 0 ? maxValue - 1 : 0);
     },
  };
}

let counter = downCounter(26);
counter.value; //=> 26
counter.next().value; //=> 25

Claramente, o downCounteré o mais puro e funcional possível. Não há nenhum problema aqui.

O protocolo do gerador usado pelo JavaScript envolve um objeto mutável. Isso não é necessário, veja o código acima. Em particular, objetos mutáveis ​​significam que perdemos transparência referencial - a capacidade de substituir uma expressão por seu valor. Enquanto no meu exemplo, counter.next().valuevai sempre ser avaliada como 25não importa onde ela ocorre e como muitas vezes repeti-lo, este não é o caso com o gerador JS - em um ponto que é 26, então 25, e que poderia realmente ser qualquer número. Isso é problemático se passarmos uma referência ao gerador para outra função:

counter.next().value; //=> 25
otherFunction(counter); // does this consume the counter?
counter.next().value; // what will this be? It depends on the otherFunction()

Tão claramente que os geradores mantêm o estado e, portanto, não são adequados para a programação funcional "pura". Felizmente, você não precisa fazer programação funcional pura e pode ser pragmático. Se os geradores tornarem seu código mais claro, você deverá usá-los sem consciência. Afinal, o JavaScript não é uma linguagem funcional pura, ao contrário de Haskell.

A propósito, em Haskell, não há diferença entre retornar uma lista e um gerador, pois ele usa uma avaliação lenta:

downCounter :: Int -> [Int]
downCounter maxValue =
  maxValue : (downCounter (max 0 (maxValue - 1)))
-- invoke as "take n (downCounter 26)" to display n elements
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.