Le funzioni del generatore sono valide nella programmazione funzionale?


17

Le domande sono:

  • I generatori rompono il paradigma della programmazione funzionale? Perché o perché no?
  • Se sì, i generatori possono essere utilizzati nella programmazione funzionale e come?

Considera quanto segue:

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

Il downCountermetodo appare apolide. Inoltre, chiamando downCountercon lo stesso input, si otterrà sempre lo stesso output. Tuttavia, allo stesso tempo, la chiamata next()non produce risultati coerenti.

Non sono sicuro che i generatori rompano o meno il paradigma di programmazione funzionale perché in questo esempio counterè un oggetto generatore e quindi la chiamata next()produrrebbe gli stessi risultati di un altro oggetto generatore creato con lo stesso identico maxValue.

Inoltre, invocare someCollection[3]un array restituirebbe sempre il quarto elemento. Allo stesso modo, chiamare next()quattro volte su un oggetto generatore restituirebbe sempre anche il quarto elemento.

Per più contesto, queste domande sono state sollevate mentre si lavorava su un kata di programmazione . La persona che ha risposto alla domanda, ha sollevato la questione se i generatori possano essere utilizzati o meno nella programmazione funzionale e se mantengano o meno lo stato.


2
Ogni programma mantiene lo stato. La vera domanda è se si qualifica come stato funzionale , che interpreto come "stato immutabile", che non cambia una volta assegnato. Sostengo che l'unico modo in cui un generatore può restituire qualcosa di diverso ad ogni chiamata è se lo stato mutevole è in qualche modo coinvolto.
Robert Harvey,

Risposte:


14

Le funzioni del generatore non sono particolarmente speciali. Possiamo implementare un meccanismo simile riscrivendo la funzione del generatore in uno stile basato sulla richiamata:

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

Chiaramente, downCounterè puro e funzionale. Non ci sono problemi qui.

Il protocollo del generatore utilizzato da JavaScript prevede un oggetto mutabile. Questo non è necessario, vedere il codice sopra. In particolare, gli oggetti mutabili significano che perdiamo la trasparenza referenziale - la capacità di sostituire un'espressione con il suo valore. Mentre nel mio esempio, counter.next().valuesarà sempre valutata come 25non importa dove si verifica e quante volte lo ripetiamo, questo non è il caso con il generatore JS - ad un certo punto è 26, allora 25, e potrebbe davvero essere un numero qualsiasi. Questo è problematico se passiamo un riferimento al generatore ad un'altra funzione:

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

Quindi, chiaramente, i generatori mantengono lo stato e non sono quindi adatti per una programmazione funzionale "pura". Fortunatamente, non è necessario eseguire una pura programmazione funzionale e potrebbe essere invece pragmatico. Se i generatori rendono il tuo codice più chiaro, dovresti usarli senza una cattiva coscienza. Dopotutto, JavaScript non è un linguaggio puramente funzionale, diversamente dall'esempio di Haskell.

A proposito, in Haskell non c'è alcuna differenza tra la restituzione di un elenco e un generatore, poiché utilizza una valutazione lazy:

downCounter :: Int -> [Int]
downCounter maxValue =
  maxValue : (downCounter (max 0 (maxValue - 1)))
-- invoke as "take n (downCounter 26)" to display n elements
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.