00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Scope e Closure in Lua

In Lua, comprendere lo scope (ambito di visibilita) delle variabili e il concetto di closure e fondamentale per scrivere codice pulito, sicuro e ben organizzato. Lua adotta un sistema di scoping lessicale che, combinato con le funzioni di prima classe, permette di creare pattern potenti come lo stato privato e le funzioni factory. In questa guida esploreremo questi concetti in dettaglio.

Variabili Locali e Globali

In Lua, le variabili dichiarate con local sono locali al blocco in cui vengono definite. Le variabili senza local sono globali e accessibili ovunque nel programma.

-- Variabile globale (sconsigliata nella maggior parte dei casi)
messaggio = "Sono globale"

-- Variabile locale
local saluto = "Sono locale"

print(messaggio)  --> Sono globale
print(saluto)     --> Sono locale

E una buona pratica usare sempre local per le variabili. Le variabili globali inquinano il namespace globale e possono causare conflitti difficili da individuare.

-- Cattiva pratica
contatore = 0  -- globale, visibile ovunque

-- Buona pratica
local contatore = 0  -- locale, visibile solo nel blocco corrente

Scoping Lessicale

Lua utilizza lo scoping lessicale (o statico): la visibilita di una variabile e determinata dalla sua posizione nel codice sorgente, non dal flusso di esecuzione.

local x = 10

local function stampa_x()
    print(x)  -- x e visibile perche dichiarata nello scope esterno
end

stampa_x()  --> 10

Le funzioni interne possono accedere alle variabili dichiarate nei blocchi che le contengono:

local function esterna()
    local valore = "ciao"

    local function interna()
        print(valore)  -- accede alla variabile dello scope esterno
    end

    interna()  --> ciao
end

esterna()

Variable Shadowing

Quando una variabile locale ha lo stesso nome di una variabile in uno scope esterno, la variabile interna nasconde (shadow) quella esterna.

local x = "esterno"

local function test()
    local x = "interno"  -- nasconde la x esterna
    print(x)  --> interno
end

test()
print(x)  --> esterno (la x originale non e stata modificata)

Lo shadowing puo verificarsi anche nei cicli:

local valore = 100

for i = 1, 3 do
    local valore = i * 10  -- shadow della variabile esterna
    print("Dentro il ciclo: " .. valore)
end

print("Fuori dal ciclo: " .. valore)
-- Output:
-- Dentro il ciclo: 10
-- Dentro il ciclo: 20
-- Dentro il ciclo: 30
-- Fuori dal ciclo: 100

Blocchi do…end per Limitare lo Scope

Lua offre i blocchi do..end per creare scope locali senza bisogno di funzioni o strutture di controllo.

local risultato

do
    local a = 10
    local b = 20
    risultato = a + b
    -- a e b esistono solo dentro questo blocco
end

print(risultato)  --> 30
-- print(a)       --> errore: a non e definita qui

Questo e utile per limitare la vita di variabili temporanee e mantenere pulito il namespace:

do
    local file = io.open("config.txt", "r")
    if file then
        local contenuto = file:read("*a")
        file:close()
        -- elaborare il contenuto...
        print("Letto: " .. #contenuto .. " caratteri")
    end
end
-- file e contenuto non sono piu accessibili qui

Cosa Sono le Closure

Una closure e una funzione che cattura e “ricorda” le variabili del suo ambiente lessicale, anche dopo che lo scope in cui e stata creata e terminato. Le variabili catturate si chiamano upvalue.

local function crea_saluto(nome)
    -- 'nome' e un upvalue per la funzione restituita
    return function()
        print("Ciao, " .. nome .. "!")
    end
end

local saluta_marco = crea_saluto("Marco")
local saluta_giulia = crea_saluto("Giulia")

saluta_marco()   --> Ciao, Marco!
saluta_giulia()  --> Ciao, Giulia!

Ogni chiamata a crea_saluto produce una closure con il proprio valore indipendente di nome.

Upvalue e Stato Mutabile

Le closure possono non solo leggere ma anche modificare i propri upvalue, mantenendo uno stato privato tra le chiamate.

local function crea_contatore(inizio)
    local conteggio = inizio or 0
    return {
        incrementa = function()
            conteggio = conteggio + 1
            return conteggio
        end,
        decrementa = function()
            conteggio = conteggio - 1
            return conteggio
        end,
        valore = function()
            return conteggio
        end
    }
end

local c = crea_contatore(0)
print(c.incrementa())  --> 1
print(c.incrementa())  --> 2
print(c.incrementa())  --> 3
print(c.decrementa())  --> 2
print(c.valore())      --> 2

La variabile conteggio e accessibile solo attraverso le tre funzioni restituite: non esiste modo di modificarla direttamente dall’esterno. Questo e un pattern di incapsulamento.

Closure come Factory Function

Le closure sono ideali per creare funzioni factory che generano funzioni specializzate.

-- Factory per moltiplicatori
local function moltiplicatore(fattore)
    return function(x)
        return x * fattore
    end
end

local doppio = moltiplicatore(2)
local triplo = moltiplicatore(3)
local per_dieci = moltiplicatore(10)

print(doppio(5))      --> 10
print(triplo(5))      --> 15
print(per_dieci(5))   --> 50

-- Factory per validatori
local function lunghezza_minima(minimo)
    return function(testo)
        return #testo >= minimo
    end
end

local almeno_3 = lunghezza_minima(3)
local almeno_8 = lunghezza_minima(8)

print(almeno_3("Lua"))     --> true
print(almeno_3("ab"))      --> false
print(almeno_8("password"))  --> true
print(almeno_8("pass"))     --> false

Closure per Stato Privato

Le closure permettono di simulare variabili private, un concetto comune nella programmazione orientata agli oggetti.

local function crea_conto_bancario(titolare, saldo_iniziale)
    local saldo = saldo_iniziale or 0
    local movimenti = {}

    local function registra(tipo, importo)
        table.insert(movimenti, {
            tipo = tipo,
            importo = importo,
            saldo_dopo = saldo
        })
    end

    return {
        deposita = function(importo)
            if importo > 0 then
                saldo = saldo + importo
                registra("deposito", importo)
                print(titolare .. ": depositato " .. importo .. " EUR")
            end
        end,
        preleva = function(importo)
            if importo > 0 and importo <= saldo then
                saldo = saldo - importo
                registra("prelievo", importo)
                print(titolare .. ": prelevato " .. importo .. " EUR")
            else
                print(titolare .. ": fondi insufficienti")
            end
        end,
        saldo_corrente = function()
            return saldo
        end,
        estratto_conto = function()
            print("--- Estratto conto di " .. titolare .. " ---")
            for _, m in ipairs(movimenti) do
                print(string.format("  %s: %.2f EUR (saldo: %.2f)",
                    m.tipo, m.importo, m.saldo_dopo))
            end
            print(string.format("  Saldo attuale: %.2f EUR", saldo))
        end
    }
end

local conto = crea_conto_bancario("Marco", 1000)
conto.deposita(500)
conto.preleva(200)
conto.preleva(2000)  --> fondi insufficienti
conto.estratto_conto()
print("Saldo: " .. conto.saldo_corrente() .. " EUR")

In questo esempio, saldo e movimenti sono completamente inaccessibili dall’esterno: l’unico modo per interagire con essi e attraverso le funzioni esposte.

Closure come Callback

Le closure sono perfette per i callback, funzioni passate come argomento che possono catturare contesto dal loro ambiente.

local function esegui_su_lista(lista, callback)
    for i, elemento in ipairs(lista) do
        callback(i, elemento)
    end
end

local totale = 0
local prezzi = {12.50, 8.99, 25.00, 3.75}

esegui_su_lista(prezzi, function(i, prezzo)
    totale = totale + prezzo  -- la closure cattura 'totale'
    print(string.format("Prodotto %d: %.2f EUR", i, prezzo))
end)

print(string.format("Totale: %.2f EUR", totale))
-- Output:
-- Prodotto 1: 12.50 EUR
-- Prodotto 2: 8.99 EUR
-- Prodotto 3: 25.00 EUR
-- Prodotto 4: 3.75 EUR
-- Totale: 50.24 EUR

Conclusioni

Lo scoping lessicale e le closure sono tra le caratteristiche piu potenti di Lua. Le variabili locali (local) garantiscono un codice pulito e privo di conflitti, i blocchi do..end permettono di limitare lo scope quando necessario e le closure consentono di creare funzioni con stato privato, factory function e callback espressivi. Padroneggiare questi concetti e essenziale per scrivere codice Lua idiomatico, modulare e manutenibile.