Ciao e bentornato alla nostra seconda lezione.
Introduzione
Nella lezione precedente, abbiamo introdotto il concetto di funzioni pure, il fondamento della programmazione funzionale. Abbiamo visto che una funzione è pura se è deterministica e non ha effetti collaterali. L'ultimo esercizio ha messo in luce un tipo comune di effetto collaterale: la modifica di un oggetto passato come argomento, come nell'esempio items.append(item). Questo ci porta direttamente all'argomento di oggi.
In questa lezione, esploreremo il concetto di immutabilità e i suoi vantaggi. L'obiettivo è spiegare i benefici dell'immutabilità per la correttezza del programma e la concorrenza. Capire perché l'immutabilità è così cruciale ti fornirà una motivazione solida per adottare uno stile di programmazione più funzionale.
1. Cos'è l'Immutabilità ?
Iniziamo con una definizione. Un oggetto immutabile è un oggetto il cui stato non può essere modificato dopo la sua creazione. Al contrario, un oggetto mutabile può essere modificato. La tua esperienza con linguaggi imperativi si basa in gran parte sulla manipolazione di dati mutabili. La programmazione funzionale favorisce l'approccio opposto.
Per un'introduzione concettuale, guarda i primi minuti del seguente video. L'analogia matematica che confronta y = x^2 - 1 con x = x^2 - 1 è particolarmente efficace per illustrare la differenza tra assegnare un nuovo valore e mutare un valore esistente.
Il video "Immutability - Computerphile" fornisce una eccellente introduzione al concetto di immutabilità e lo contrappone alla mutabilità .
Guarda la prima parte del video, fino al timestamp 01:35. Concentrati sulla definizione di immutabilità e sull'idea che, invece di cambiare un oggetto, se ne crea uno nuovo.
In Python, questa distinzione è fondamentale. Alcuni dei tipi di dati che conosci bene sono immutabili, mentre altri sono mutabili.

Quando scrivi my_string = "Hello" e poi my_string = my_string + " World", non stai modificando la stringa originale "Hello". Stai invece creando una nuova stringa, "Hello World", e stai facendo in modo che la variabile my_string punti a questo nuovo oggetto. L'oggetto originale non è cambiato. Con una lista, invece, my_list.append(1) modifica l'oggetto lista in-place.
Questa distinzione è la chiave per comprendere i benefici che stiamo per esplorare.
2. Beneficio 1: Correttezza del Programma
Il vantaggio più immediato e pervasivo dell'immutabilità è la capacità di scrivere codice più prevedibile e meno soggetto a bug. Il problema principale con i dati mutabili deriva da un fenomeno chiamato aliasing: quando più variabili si riferiscono allo stesso oggetto mutabile in memoria. Una modifica tramite un alias influisce su tutte le altre, spesso in modi inaspettati.
L'articolo seguente del MIT, sebbene utilizzi esempi in Java, illustra questo rischio in modo eccezionale. I concetti sono universali e si applicano perfettamente a Python.
Reading 9: Mutability & Immutability
L'articolo "Reading 9: Mutability & Immutability" del MIT mostra, con esempi pratici molto chiari, come la mutabilità possa portare a bug difficili da scovare.
Leggi la sezione intitolata "Risks of mutation", che contiene due esempi: "Risky example #1: passing mutable values" e "Risky example #2: returning mutable values". Analizza come le modifiche (mutazioni) a una lista e a un oggetto Date portino a risultati inaspettati e errati.
Riflettiamo sugli esempi dell'articolo:
- Passare valori mutabili (
sumAbsolute): La funzionesumAbsolutemodifica la lista che riceve come input. Questo "effetto collaterale" corrompe i dati del chiamante (myData), che probabilmente si aspettava che la sua lista rimanesse invariata. Questo rompe l'isolamento tra le parti del codice. - Restituire valori mutabili (
startOfSpring): La funzionestartOfSpringrestituisce un riferimento a un oggettoDateinterno (nella versione con cache). Quando il client modifica questo oggetto (partyDate.setMonth(...)), sta involontariamente alterando lo stato interno della cache, causando problemi a tutti gli altri client della funzione. Questo rompe l'incapsulamento.
Per rendere il problema ancora più concreto in Python, guarda il seguente spezzone video. Mostra esattamente lo stesso tipo di problema discusso teoricamente, ma con una struttura dati che useresti tutti i giorni in Python: una lista di dizionari.
Functional Programming in Python: Immutable Data Structures
Il video "Functional Programming in Python: Immutable Data Structures" di Real Python mostra un esempio pratico di come i dati mutabili in Python possano portare a modifiche indesiderate.
Guarda la sezione dal timestamp 05:30 al 07:43. Osserva come la modifica del nome di uno scienziato tramite un accesso diretto alla lista alteri la fonte dati originale.
In sintesi, gli oggetti immutabili agiscono come un contratto: garantiscono che i dati che passi o ricevi non possano essere cambiati di nascosto. Questo rende il flusso dei dati esplicito e il comportamento del programma molto più facile da prevedere.
3. Beneficio 2: Semplicità nella Concorrenza
Il secondo grande vantaggio emerge nel contesto della programmazione concorrente e parallela, un'esigenza sempre più critica con le moderne CPU multi-core.
Quando più thread o processi tentano di leggere e scrivere su una stessa area di memoria condivisa (uno stato mutabile), possono sorgere problemi complessi come le race conditions, che portano a dati corrotti e risultati imprevedibili.
L'approccio tradizionale, che probabilmente conosci, consiste nell'usare meccanismi di sincronizzazione come lock o semafori per proteggere l'accesso ai dati condivisi. Questo approccio è notoriamente difficile da implementare correttamente e può introdurre altri problemi, come i deadlock.
L'articolo seguente illustra questo problema con un chiaro esempio in Python.
Mutability And Immutability of Variables in Python
L'articolo "Mutability And Immutability of Variables in Python" mostra un esempio pratico di race condition quando si usano thread con una lista mutabile.
Leggi la sezione "The Mutability Issue: Working With Threads". Analizza l'esempio di codice e capisci perché l'output finale di my_list può essere imprevedibile. Successivamente, leggi rapidamente l'inizio della sezione successiva che mostra la soluzione con threading.Lock. Nota come il codice diventi più complesso per gestire la sincronizzazione.
L'immutabilità offre una soluzione radicalmente più semplice: se i dati non possono essere modificati, non c'è nulla da proteggere.
Più thread possono leggere lo stesso dato immutabile simultaneamente senza alcun rischio di conflitto. Non sono necessari lock, semafori o altre primitive di sincronizzazione complesse. Questo elimina intere classi di bug legati alla concorrenza.
Il video di Computerphile che abbiamo visto all'inizio spiega molto bene questo concetto a un livello più alto, confrontando l'architettura a memoria condivisa (tipica della mutabilità ) con quella a passaggio di messaggi (favorita dall'immutabilità ).
Torniamo al video di Computerphile per approfondire come l'immutabilità risolve i problemi della concorrenza.
Guarda la sezione che va dal timestamp 01:35 al 05:22. Concentrati sulla distinzione tra concorrenza con stato mutabile (memoria condivisa, lock) e con stato immutabile (nessuna memoria condivisa, passaggio di messaggi). Presta attenzione ai benefici menzionati, come l'isolamento dei fallimenti (fault isolation).
Come sottolinea il video, questo approccio non solo semplifica il codice, ma rende anche i sistemi più resilienti. Se un processo si arresta in modo anomalo, non può lasciare la memoria condivisa in uno stato corrotto, perché non c'è memoria condivisa.
Riepilogo e Prossimi Passi
In questa lezione abbiamo esplorato il "perché" dietro uno dei pilastri della programmazione funzionale.
Punti chiave:
- L'immutabilità impedisce che lo stato di un oggetto venga modificato dopo la sua creazione, costringendo a creare nuovi oggetti per rappresentare nuovi stati.
- Migliora la correttezza del programma eliminando i bug causati da effetti collaterali nascosti dovuti all'aliasing di oggetti mutabili. Rende il codice più prevedibile e più facile da analizzare.
- Semplifica drasticamente la programmazione concorrente eliminando la necessità di complessi e fragili meccanismi di sincronizzazione come i lock, prevenendo così le race conditions per design.
Ovviamente, c'è un compromesso, spesso legato alle prestazioni dovute alla creazione di nuovi oggetti. Tuttavia, come accennato nel video di Computerphile, le architetture moderne multi-core rendono i vantaggi della concorrenza offerti dall'immutabilità sempre più importanti.
Ora che abbiamo capito perché l'immutabilità è così potente, il passo successivo è imparare a usarla in modo efficace.
Nella prossima lezione, vedremo come applicare questi principi in Python, esplorando le strutture dati immutabili native del linguaggio: tuple e frozenset.