Perché l'utilizzo dell'operatore di assegnazione o dei loop scoraggiati nella programmazione funzionale?


9

Se la mia funzione soddisfa due requisiti al di sotto, credo che la funzione Sum restituisca la somma degli elementi in un elenco in cui l'articolo viene valutato come vero per una determinata condizione si qualifica come funzione pura, non è vero?

1) Per un dato set di i / p, la stessa o / p viene restituita indipendentemente dal tempo in cui viene chiamata la funzione

2) Non ha alcun effetto collaterale

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if(predicate(item)) result += item;
    return result;
}

Esempio : Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});

Il motivo per cui sto ponendo questa domanda è perché vedo quasi tutte le persone che consigliano di evitare l'operatore e i cicli di assegnazione perché è uno stile di programmazione imperativo. Quindi cosa può andare storto con l'esempio sopra che utilizza i loop e l'operatore di assegnazione nel contesto della programmazione delle funzioni?


1
Non ha alcun effetto collaterale - ha un effetto collaterale, quando la itemvariabile è mutata nel loop.
Fabio,

@Fabio ok. Ma puoi approfondire la portata dell'effetto collaterale?
rahulaga_dev,

Risposte:


16

Cosa c'è nella programmazione funzionale che fa la differenza?

La programmazione funzionale è per principio dichiarativa . Dici qual è il tuo risultato invece di come calcolarlo.

Diamo un'occhiata all'implementazione davvero funzionale del tuo snippet. In Haskell sarebbe:

predsum pred numbers = sum (filter pred numbers)

È chiaro quale sia il risultato? Proprio così, è la somma dei numeri che soddisfano il predicato. Come viene calcolato? Non mi interessa, chiedi al compilatore.

Potresti dire che usare sumed filterè un trucco e non conta. Lascialo implementare senza questi helper (anche se il modo migliore sarebbe implementarli prima).

La soluzione "Programmazione funzionale 101" che non utilizza sumè con la ricorsione:

sum pred list = 
    case list of
        [] -> 0
        h:t -> if pred h then h + sum pred t
                         else sum pred t

È ancora abbastanza chiaro quale sia il risultato in termini di chiamata a funzione singola. È 0, o recursive call + h or 0, a seconda di pred h. Ancora piuttosto diretto, anche se il risultato finale non è immediatamente ovvio (anche se con un po 'di pratica questo si legge proprio come un forciclo).

Confrontalo con la tua versione:

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if (predicate(item)) result += item;
    return result;
}

Qual'è il risultato? Oh, capisco: singola returndichiarazione, nessuna sorpresa qui: return result.

Ma cos'è result? int result = 0? Non sembra giusto. Fai qualcosa dopo 0. Ok, aggiungi items. E così via.

Naturalmente, per la maggior parte dei programmatori questo è abbastanza ovvio cosa succede in una semplice funzione come questa, ma aggiungi qualche returndichiarazione in più e diventa improvvisamente più difficile da tracciare. Tutto il codice riguarda come e cosa deve capire il lettore: questo è chiaramente uno stile molto imperativo .

Quindi, le variabili e i loop sono sbagliati?

No.

Esistono molte cose che sono molto più facili da spiegare e molti algoritmi che richiedono uno stato mutabile per essere veloci. Ma le variabili sono intrinsecamente imperative, spiegano come invece di cosa e dando una piccola previsione di quale possa essere il loro valore poche righe più tardi o dopo alcune ripetizioni cicliche. I circuiti richiedono generalmente che lo stato abbia un senso, e quindi sono anche intrinsecamente imperativi.

Variabili e loop non sono semplicemente una programmazione funzionale.

Sommario

La programmazione contemporanea funzionale è un po 'più di stile e un modo di pensare utile di un paradigma. La forte preferenza per le funzioni pure è in questa mentalità, ma in realtà è solo una piccola parte.

Le lingue più diffuse consentono di utilizzare alcuni costrutti funzionali. Ad esempio in Python puoi scegliere tra:

result = 0
for num in numbers:
    if pred(result):
        result += num
return result

o

return sum(filter(pred, numbers))

o

return sum(n for n in numbers if pred(n))

Queste espressioni funzionali si adattano perfettamente a quel tipo di problemi e rendono semplicemente il codice più breve (e più breve è buono ). Non dovresti sostituire senza pensarci il codice imperativo con loro, ma quando si adattano, sono quasi sempre una scelta migliore.


grazie per una bella spiegazione !!
rahulaga_dev,

1
@RahulAgarwal Potresti trovare interessante questa risposta , che cattura piacevolmente un concetto tangenziale di stabilire verità invece di descrivere i passi. Mi piace anche la frase "i linguaggi dichiarativi contengono effetti collaterali mentre i linguaggi imperativi no" - di solito i programmi funzionali hanno tagli netti e ben visibili tra il codice stateful che tratta il mondo esterno (o eseguendo un algoritmo ottimizzato) e il codice puramente funzionale.
Frax,

1
@Frax: grazie !! Lo esaminerò. Anche di recente si è imbattuto in Rich Hickey sul valore dei valori. È davvero geniale. Penso a una regola empirica: "lavorare con i valori e l'espressione piuttosto che lavorare con qualcosa che ha valore e può cambiare"
rahulaga_dev

1
@Frax: Inoltre è giusto affermare che FP è un'astrazione sulla programmazione imperativa - perché alla fine qualcuno deve istruire la macchina su "come fare", giusto? Se sì, allora la programmazione imperativa non ha un controllo di livello più basso rispetto a FP?
rahulaga_dev,

1
@Frax: concordo con Rahul che l'imperativo è di livello inferiore, nel senso che è più vicino alla macchina sottostante. Se l'hardware potesse effettuare copie dei dati gratuitamente, non avremmo bisogno di aggiornamenti distruttivi per migliorare l'efficienza. In questo senso, il paradigma imperativo è più vicino al metallo.
Giorgio,

9

L'uso dello stato mutevole è generalmente scoraggiato nella programmazione funzionale. I loop sono scoraggiati di conseguenza, poiché i loop sono utili solo in combinazione con lo stato mutabile.

La funzione nel suo insieme è pura, il che è grandioso, ma il paradigma della programmazione funzionale non si applica solo a livello di funzioni intere. Volete anche evitare lo stato mutevole anche a livello locale, all'interno delle funzioni. E il ragionamento è sostanzialmente lo stesso: evitare lo stato mutabile rende il codice più facile da capire e previene alcuni bug.

Nel tuo caso, potresti scrivere numbers.Where(predicate).Sum()che è chiaramente molto più semplice. E più semplice significa meno bug.


grazie !! Penso che mi mancasse una linea sorprendente - ma il paradigma della programmazione funzionale non si applica solo a livello di intere funzioni, ma ora mi chiedo anche come visualizzare questo confine. Fondamentalmente dal punto di vista del consumatore è pura funzione ma lo sviluppatore che ha effettivamente scritto questa funzione non ha seguito le linee guida di pura funzione? confuso :(
rahulaga_dev il

@RahulAgarwal: quale confine?
Jacques B

Sono confuso in senso se il paradigma di programmazione si qualifica come FP dal punto di vista del consumatore delle funzioni? Bcoz se guardo l'implementazione dell'implementazione di Wherein numbers.Where(predicate).Sum()- fa uso di foreachloop.
rahulaga_dev,

3
@RahulAgarwal: In quanto consumatore di una funzione, non ti interessa davvero se una funzione o un modulo utilizza internamente uno stato mutabile purché sia ​​esternamente puro.
Jacques B

7

Mentre hai ragione sul fatto che dal punto di vista di un osservatore esterno, la tua Sumfunzione è pura, l'implementazione interna non è chiaramente pura - hai uno stato memorizzato in resultcui muti ripetutamente. Uno dei motivi per evitare lo stato mutevole è perché produce un maggiore carico cognitivo sul programmatore, che a sua volta porta a più bug [citazione necessaria] .

Mentre in un semplice esempio come questo, la quantità di stato mutabile memorizzato è probabilmente abbastanza piccola da non causare problemi gravi, il principio generale si applica comunque. Un esempio di giocattolo come Sumprobabilmente non è il modo migliore per illustrare il vantaggio della programmazione funzionale rispetto all'imperativo: prova a fare qualcosa con molto stato mutevole e i vantaggi potrebbero diventare più chiari.

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.