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

15

As perguntas são:

  • Os geradores quebram o paradigma de programação funcional? Por que ou por que não?
  • Se sim, geradores de lata podem ser usados em 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 método downCounter aparece sem estado. Além disso, chamar downCounter com a mesma entrada sempre resultará na mesma saída. No entanto, ao mesmo tempo, chamar 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, assim, chamar next() produziria os mesmos resultados que outro objeto gerador criado exatamente com o mesmo maxValue .

Além disso, chamar someCollection[3] em 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 uma kata de programação . A pessoa que respondeu à pergunta levantou a questão de saber se os geradores poderiam ou não ser usados em programação funcional e se eles mantêm ou não o estado.

    
por Pete 29.09.2016 / 20:51
fonte

1 resposta

12

As funções do gerador não são particularmente especiais. Podemos implementar um mecanismo semelhante por meio de reescrever a função geradora 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 é tão puro e funcional quanto possível. Não há problema aqui.

O protocolo 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 pelo seu valor. Enquanto no meu exemplo, counter.next().value irá sempre avaliar para 25 não importa onde ocorra e quantas vezes nós o repetimos, este não é o caso com o gerador JS - em um ponto ele é 26 e, em seguida, 25 , e poderia 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()

Então, claramente, os geradores mantêm o estado e, portanto, são inadequados para 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ê deve usá-los sem uma má consciência. Afinal, o JavaScript não é uma linguagem funcional pura, ao contrário de, por exemplo, Haskell.

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

downCounter :: Int -> [Int]
downCounter maxValue =
  maxValue : (downCounter (max 0 (maxValue - 1)))
-- invoke as "take n (downCounter 26)" to display n elements
    
por 29.09.2016 / 22:02
fonte