Perché l'utilizzo dell'operatore o dei cicli di assegnazione è scoraggiato nella programmazione funzionale?

9

Se la mia funzione soddisfa i due requisiti, credo che la funzione Sum per restituire la somma di elementi in una lista in cui l'elemento restituisce true per una determinata condizione si qualifica come pura funzione, isn vero?

1) Per un dato set di i / p, lo stesso o / p viene restituito indipendentemente dal momento in cui viene chiamata la funzione

2) Non ha effetti collaterali

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});

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

    
posta Rahul Agarwal 21.10.2018 - 11:40
fonte

3 risposte

16

Che cos'è nella programmazione funzionale che fa la differenza?

La programmazione funzionale è in linea di principio dichiarativa . Hai detto quale risultato è invece di come per calcolarlo.

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

predsum pred numbers = sum (filter pred numbers)

È chiaro che il risultato è? Abbastanza, è la somma dei numeri che soddisfano il predicato. In che modo viene calcolato? Non mi interessa, chiedi al compilatore.

Potresti dire che usare sum e filter è un trucco e non conta. Consentitemi di implementarlo senza questi helper (anche se il modo migliore sarebbe implementarli prima).

La soluzione "Programmazione funzionale 101" che non usa sum è con 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 che è il risultato in termini di chiamata a funzione singola. È 0 o recursive call + h or 0 , a seconda di pred h . Ancora piuttosto semplice, anche se il risultato finale non è immediatamente ovvio (anche se con un po 'di pratica questo si legge proprio come un for loop).

Confronta questo 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, vedo: singola dichiarazione return , nessuna sorpresa qui: return result .

Ma cos'è result ? %codice%? Non sembra giusto. Fai qualcosa più tardi con quel int result = 0 . Ok, aggiungi 0 s ad esso. E così via.

Ovviamente, per la maggior parte dei programmatori è abbastanza ovvio che cosa succede in una semplice funzione come questa, ma aggiungi qualche extra% di% di enunciazione e quindi diventa improvvisamente più difficile da tracciare. Tutto il codice riguarda come e cosa è lasciato al lettore per capire - questo è chiaramente uno stile molto imperativo .

Quindi, le variabili e i cicli sono errati?

No.

Ci sono molte cose che sono molto più facili da spiegare e molti algoritmi che richiedono uno stato mutabile sono veloci. Ma le variabili sono intrinsecamente imperative, spiegando come invece di cosa e dando poche previsioni su quale possa essere il loro valore poche righe dopo o dopo alcune iterazioni del ciclo. I loop generalmente richiedono che lo stato abbia senso, e quindi sono intrinsecamente imperativi pure.

Le variabili e i loop non sono semplicemente programmazione funzionale.

Sommario

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

La maggior parte dei linguaggi diffusi ti consente 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 bene a questo tipo di problemi e rendono semplicemente il codice più breve (e più corto è buono ). Non dovresti sostituire irrispettosamente il codice imperativo con loro, ma quando si adattano, sono quasi sempre una scelta migliore.

    
risposta data 21.10.2018 - 13:47
fonte
9

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

La funzione nel suo complesso è pura, il che è grande, ma il paradigma della programmazione funzionale non si applica solo a livello di funzioni intere. Inoltre, si desidera evitare lo stato mutabile anche a livello locale, all'interno delle funzioni. E il ragionamento è fondamentalmente 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.

    
risposta data 21.10.2018 - 11:57
fonte
7

Sebbene tu sia corretto dal punto di vista di un osservatore esterno, la tua funzione Sum è pura, l'implementazione interna non è chiaramente pura - hai lo stato memorizzato in result che muti ripetutamente. Uno dei motivi per evitare lo stato mutabile è perché produce un carico cognitivo maggiore sul programmatore, che a sua volta porta a più bug [citazione necessaria] .

Mentre in un semplice esempio come questo, la quantità di stato mutabile che si sta memorizzando è probabilmente abbastanza piccola da non causare alcun problema serio, il principio generale si applica ancora. Un esempio di giocattolo come Sum probabilmente non è il modo migliore per illustrare il vantaggio della programmazione funzionale oltre all'imperativo: prova a fare qualcosa con un lotto di stato mutabile e i vantaggi potrebbero diventare più chiari.

    
risposta data 21.10.2018 - 12:01
fonte