Le funzioni del generatore sono valide nella programmazione funzionale?

15

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 in che modo?

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 metodo downCounter appare stateless. Inoltre, chiamando downCounter con lo stesso input, otterrai sempre lo stesso risultato. Tuttavia, allo stesso tempo, chiamare next() non produce risultati coerenti.

Non sono sicuro che i generatori interrompano il paradigma della programmazione funzionale perché in questo esempio counter è un oggetto generatore e quindi chiamare next() produrrebbe gli stessi risultati di un altro oggetto generatore creato con lo stesso esatto maxValue .

Inoltre, chiamare someCollection[3] su un array restituirebbe sempre il quarto elemento. Analogamente, chiamare next() quattro volte su un oggetto generatore restituirà sempre anche il quarto elemento.

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

    
posta Pete 29.09.2016 - 20:51
fonte

1 risposta

12

Le funzioni del generatore non sono particolarmente speciali. Possiamo implementare un meccanismo simile noi stessi riscrivendo la funzione del generatore in uno stile basato sul callback:

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 c'è nessun problema qui.

Il protocollo del generatore usato da JavaScript coinvolge un oggetto mutabile. Questo non è necessario, vedere il codice sopra. In particolare, gli oggetti mutabili significano perdere trasparenza referenziale - la capacità di sostituire un'espressione con il suo valore. Mentre nel mio esempio, counter.next().value sempre valuterà a 25 indipendentemente da dove si verifica e quanto spesso lo ripetiamo, questo non è il caso con il generatore JS - ad un certo punto è 26 , quindi 25 , e potrebbe essere davvero 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 hanno uno stato e sono quindi inadatti alla programmazione funzionale "pura". Fortunatamente, non devi fare pura programmazione funzionale, e invece puoi essere pragmatico. Se i generatori rendono il tuo codice più chiaro, dovresti usarli senza una cattiva coscienza. Dopotutto, JavaScript non è un linguaggio funzionale puro, a differenza ad es. Haskell.

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

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