CodeGym /Corsi /C# SELF /Introduzione alla programmazione funzionale

Introduzione alla programmazione funzionale

C# SELF
Livello 51 , Lezione 0
Disponibile

1. Introduzione

La programmazione funzionale (PF) è un paradigma di programmazione dove il mattone fondamentale non è l'oggetto né la procedura/metodo, ma la funzione in senso matematico. Nella PF l'attenzione è posta sulla descrizione di "cosa calcolare", non su "come calcolarlo".

Avete già incontrato singole idee della PF quando avete lavorato con le espressioni lambda e LINQ. Ma qual è la differenza? In pratica: l'OOP descrive oggetti e le loro interazioni, la programmazione procedurale è un insieme di passi, mentre la PF è la composizione di funzioni, il passaggio del comportamento come valore, il rifiuto della modifica dello stato (immutability) e l'assenza di side effects.

Perché adottare un nuovo paradigma?

  • Codice più pulito, prevedibile e testabile.
  • Semplificazione del supporto al multithreading ("niente stato — nessun problema").
  • Conciseness e espressività (meno codice = meno bug).
  • Astrazioni di alto livello, facilmente riutilizzabili.

Analogia

Immagina che un ristorante riceva un ordine: "preparare un'omelette". Lo chef imperativo esegue una lista di istruzioni: prendere le uova, romperle, sbatterle, friggere. Lo chef funzionale dice: res = omelette(uova) — opera con funzioni, astraendo dallo stato interno della cucina (beh, quasi).

In C# possiamo usare entrambi gli approcci. Questo rende il linguaggio molto flessibile e potente — specialmente nei progetti reali.

Concetti chiave della PF

1. Funzioni di ordine superiore

Le funzioni possono essere passate come parametri, restituite da altre funzioni e memorizzate in variabili. Avete già fatto questo con le espressioni lambda e i delegate. Nella PF queste "funzioni su funzioni" sono la base di tutto.

2. Funzioni pure

Una funzione è "pura" se il suo risultato dipende solo dai parametri e non modifica nulla al di fuori di sé (no side effects). Due chiamate identiche con gli stessi argomenti danno lo stesso risultato.

3. Immutabilità (Immutability)

I dati non vengono mutati "in place": il nuovo stato è un nuovo oggetto. Questo semplifica molto il ragionamento sul programma e aiuta nel multithreading.

4. Assenza di side effects

Una funzione non scrive su file, non modifica variabili globali, non disegna sullo schermo — semplicemente restituisce un risultato. Nella vita reale i side effects sono inevitabili, ma si cerca di isolarli ai bordi del sistema.

5. Composizione di funzioni

Una funzione può essere costruita da altre, come mattoncini. Per esempio: filtrare i numeri positivi, prendere i loro quadrati e sommarli. Ogni operazione è una funzione separata e si combinano facilmente (WhereSelectSum).

2. PF in C#: dalla teoria alla pratica

C# è un linguaggio multiparadigma: supporta bene OOP, l'approccio procedurale e uno stile funzionale potente (con lambda, delegate, extension methods e LINQ).

Vediamo un esempio nella nostra app didattica

Immaginiamo di sviluppare un programma per lavorare con liste di numeri e stringhe. Il nostro compito è applicare a questi dati varie operazioni in stile funzionale.

Esempio 1: Uso delle funzioni di ordine superiore


// Applica un'azione a tutti gli elementi della lista
public static void ForEach<T>(List<T> items, Action<T> action)
{
    foreach (var item in items)
    {
        action(item);
    }
}

Utilizzo:


var numbers = new List<int> { 1, 2, 3, 4, 5 };
ForEach(numbers, n => Console.WriteLine(n * n)); // Funzione-parametro

Visto? Una funzione può essere "messa in una variabile" o passata come normale valore — proprio come una mela in cucina!

Esempio 2: Funzione pura

Una funzione che non modifica lo stato del programma e dipende solo dall'input:


int MultiplyByTwo(int x)
{
    return x * 2;
}
  • Non dipende da nulla di esterno.
  • Non modifica nulla esternamente.
  • Per x = 5 restituirà sempre 10.

Confronta con una funzione che usa e modifica una variabile globale:


int total = 0;
int AddToTotal(int x)
{
    total += x;
    return total;
}

Questa non è più una funzione pura — il risultato dipende dallo stato esterno e la funzione lo modifica.

Esempio 3: Immutabilità dei dati

Invece di modificare i dati in ingresso creiamo nuovi oggetti:


List<int> AddOneToEach(List<int> numbers)
{
    return numbers.Select(n => n + 1).ToList();
}

La lista di input non viene modificata. Nei programmi multithread questo è particolarmente comodo: meno lock e meno race.

Esempio 4: Composizione di funzioni

Ottenere la somma dei quadrati di tutti i numeri pari:


int SumOfEvenSquares(List<int> numbers)
{
    return numbers
        .Where(n => n % 2 == 0)     // Tenere solo i pari
        .Select(n => n * n)         // Elevare al quadrato
        .Sum();                     // Sommare
}

Leggibile e dichiarativo: ogni operazione è una funzione separata.

3. Sfumature utili

PF, LINQ e C#

LINQ è quasi "PF in pratica" per le collezioni: usi funzioni di ordine superiore (Where, Select ecc.), ottieni nuove sequence senza mutare quelle originali, e ogni trasformazione è un'espressione a sé. Il risultato è IEnumerable<T>, che descrive cosa ottenere, non come iterare.

Tabella di analogie

Imperativo (procedurale/OOP) Funzionale (LINQ/stile PF)
foreach (var x in xs) ...
xs.Select(...)
«Mutare» la collezione Ottenere una nuova collezione
Stato (total += x) Funzioni pure (xs.Sum())
Descrivere come: «fai questo» Descrivere: «cosa vogliamo ottenere»

PF vs OOP: due mondi — un solo C#

Non sono campi in competizione. Nei progetti C# reali li si combina: il modello del dominio (domain model) è comodo da costruire con classi (OOP), mentre il processamento delle collezioni, l'aggregazione dei dati e le trasformazioni si fanno in stile funzionale con LINQ, lambda ed extension methods.

La tua conoscenza dei delegate è direttamente utile: Func<T, TResult>, Predicate<T>, Action<T> — sono i mattoni tipici dello stile PF.

Funzione universale di filtraggio:


List<T> Filter<T>(List<T> items, Predicate<T> predicate)
{
    var result = new List<T>();
    foreach (var item in items)
    {
        if (predicate(item))
            result.Add(item);
    }
    return result;
}

Chiamate:


var adults = Filter(people, person => person.Age >= 18);
var bigFiles = Filter(fileNames, name => name.EndsWith(".mp4") && name.Length > 10);

Invece di molte funzioni simili con condizioni diverse — una sola funzione universale.

Perché i datori di lavoro e i colloqui apprezzano sviluppatori PF?

  • PF aiuta a testare piccoli blocchi di codice senza avviare tutto il sistema.
  • La logica è più semplice da mantenere: meno stati = meno sorgenti di bug.
  • È più semplice scrivere codice parallelo e asincrono — niente stato globale, meno race.

E come non diventare fanatici?

Sì, la PF è potente. Ma C# non è un linguaggio puramente funzionale, e non tutte le task richiedono purezza perfetta. Non temere variabili locali e mutazioni ragionevoli dove hanno senso. L'importante è leggibilità, prevedibilità e testabilità. Gli elementi PF sono uno strumento, non una religione.

4. Errori tipici dei principianti

È molto facile imbrogliarsi con codice che sembra funzionale ma in realtà non lo è.

Per esempio, una funzione restituisce una nuova collezione, ma lungo il percorso muta la lista originale — questo viola il principio di immutabilità e rompe le aspettative del chiamante.

Altro esempio: una lambda accede a una variabile esterna e la modifica. Nella paradigma funzionale questo è considerato un side effect e rende il comportamento del codice meno prevedibile.

Il compilatore C# non ti fermerà: il linguaggio permette entrambe le cose. Perciò, nelle pratiche PF è importante assicurarsi che una funzione "viva da sola", non modifichi nulla all'esterno e non legga altro se non i suoi argomenti.

2
Compito
C# SELF, livello 51, lezione 0
Bloccato
Funzione di filtraggio di una lista
Funzione di filtraggio di una lista
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION