Tutorial su Lua
Lua è un linguaggio di scripting sviluppato in Brasile all’inizio degli anni ‘90. Il codice sorgente di un programma Lua viene tradotto in byte code ed eseguito dall’interprete Lua. L’interprete stesso è scritto in C, il che aiuta i programmi Lua a ottenere prestazioni elevate. Inoltre, l’API C consente al codice Lua di essere incorporato in programmi C/C++. Lua è un linguaggio multiparadigma adatto a scrivere codice imperativo, funzionale e orientato agli oggetti.
Il principale punto di forza di Lua è la facile integrazione in altri sistemi e linguaggi. Lua si è così affermato come “glue language” ed è utilizzato in molti motori grafici di videogiochi. Il linguaggio può essere utilizzato anche per controllare server web come Apache e nginx. Lua viene spesso usato come linguaggio di programmazione Internet autonomo tramite l’interfaccia CGI. Inoltre, questo linguaggio viene utilizzato anche per la programmazione di app mobili.
Tutorial sul linguaggio di scripting Lua: i primi passi
Il modo più semplice e veloce per imparare a programmare con Lua è eseguire il codice sulla pagina demo Lua interattiva. Sarà possibile testare lì tutti gli esempi di codice Lua presentati più avanti in questo articolo. Basterà copiare uno degli esempi di codice nel campo di testo e fare clic su “run” per eseguire il codice.
In questo modo non dovete fare subito l’installazione. Se desiderate utilizzare Lua sul vostro sistema, seguite le nostre istruzioni. Altrimenti, andate direttamente alla sezione “Imparare le basi del linguaggio di scripting Lua”.
Preparare il proprio sistema per il tutorial su Lua
L’interprete Lua consiste in un singolo file binario disponibile sulla riga di comando dopo aver dato il comando “lua”. Questo viene memorizzato nel sistema e potrebbe dover essere incluso nel percorso. Lua offre anche librerie che consentono l’incorporamento del codice nei programmi C/C++.
Per Mac e Linux, l’ideale è l’installazione con il gestore di pacchetti “Homebrew”. Se avete installato Homebrew sul vostro sistema, è meglio se utilizzate il seguente codice sulla riga di comando per installare Lua:
brew install lua
Per installare Lua su un sistema Windows potete utilizzare LuaDist Installer.
Utilizzare l’interprete Lua in modo interattivo
Come spesso accade con i linguaggi di scripting, è possibile eseguire l’interprete Lua in modo interattivo. In modalità interattiva, l’interprete riceve il codice Lua sulla riga di comando e lo esegue riga per riga. I valori generati vengono visualizzati direttamente sulla riga di comando. Come utente, è possibile dunque controllare e modificare i valori delle variabili. Questo approccio è quindi particolarmente adatto per la prototipazione rapida. Per avviare l’interprete Lua in modalità interattiva, bisogna eseguire il seguente comando sulla riga di comando:
# Avviare l’interprete Lua in modalità interattiva
lua -i
Per uscire di nuovo dalla modalità interattiva, immettete il comando “os.exit ()” o premete i tasti [Ctrl] + [D] contemporaneamente.
Eseguire lo script Lua per il tutorial con l’interprete
Invece di inserire il codice Lua pezzo per pezzo sulla riga di comando, potete istruire l’interprete Lua a eseguire un file di codice sorgente Lua completo. Per fare ciò, bisogna prima di tutto creare un file Lua e passare il nome del file all’interprete per l’esecuzione. L’interprete legge riga per riga il codice sorgente contenuto nel file e lo esegue.
# Eseguire lo script Lua
lua <nomefile> .lua
I file di codice sorgente Lua terminano con l’estensione “.lua”.
Rendere lo script Lua per il tutorial direttamente eseguibile usando uno shebang
Sui sistemi operativi Linux / UNIX / macOS, abbiamo anche la possibilità di rendere direttamente eseguibile un file di codice sorgente Lua. Per fare ciò, dobbiamo inserire un cosiddetto “shebang” come prima riga del file Lua:
#!/usr/local/bin/lua
-- Codice Lua per l’esecuzione
Osserviamo come lo shebang contenga la posizione del file binario Lua, nel nostro caso “#!/usr/local/bin/lua”. La posizione di archiviazione sul sistema locale potrebbe essere diversa. In questo caso, potete determinare la posizione del file binario Lua. A tale scopo, utilizzate il comando “which” sulla riga di comando:
# Determinare la posizione del file binario Lua
which lua
Dopo aver eseguito lo shebang di uno script Lua, dovete contrassegnare il file come eseguibile dall’utente. Quindi utilizzate il seguente codice sulla riga di comando:
# Contrassegnare il file Lua come eseguibile
chmod u + rx <nomefile> .lua
Successivamente eseguite lo script Lua nella directory corrente:
./<nomefile>.lua
Il trucco shebang funziona su sistemi Linux e UNIX come macOS con la maggior parte dei linguaggi di scripting. Potete rendere gli script Ruby o Python direttamente eseguibili secondo lo stesso schema.
Imparare le basi del linguaggio di scripting Lua
Lua è un linguaggio multiparadigma; lo stile di base è imperativo e funzionale. Il linguaggio è completamente dinamico, ovvero non viene fatta alcuna distinzione tra “compile time” e “runtime”. Lua si affida alla gestione dinamica della memoria, senza eccezioni: la dimensione di un oggetto in memoria può cambiare durante il runtime. Un “Garbage Collector” (GC) libera lo spazio in memoria che non è più necessario in modo che il programmatore non debba preoccuparsene.
Imparare a usare i commenti negli script Lua
I commenti sono una parte essenziale di qualsiasi linguaggio di programmazione. Fra gli altri, sono utilizzati per i seguenti scopi:
- Fare bozze delle componenti del codice
- Documentare le caratteristiche del codice
- Attivare / disattivare le righe di codice
Un commento a riga singola in Lua inizia con un doppio trattino (--) e arriva alla fine della riga:
-- Questa riga viene ignorata dall’interprete
In combinazione con le doppie parentesi quadre “[[“ e “]]” è possibile scrivere anche commenti su più righe:
--[[
Questo commento
si estende su
più righe.
]]
Valori e tipi per il tutorial del linguaggio di scripting Lua
Come la maggior parte degli altri linguaggi di scripting, Lua è un linguaggio tipizzato dinamicamente. I tipi non appartengono a variabili, ma a valori. Ogni valore ha un tipo specifico, sia esso un numero, una stringa di caratteri, un valore logico, ecc. Nel complesso, Lua ha una quantità gestibile di tipi, che sono riassunti qui di seguito:
Tipo | Spiegazione |
number | numero decimale |
string | stringa |
boolean | valore di verità: “true” o “false” |
nil | valore mancante; il tipo ha solo il valore di “nil” |
function | funzione |
table | tipo di dato composto: lista / array, hash / dictionary |
thread | co-routine |
userdata | tipo di dati C definito dall’utente |
Lua conosce la sintassi letterale per i valori di tutti i tipi ad eccezione di “thread” e “userdata”. Per determinare il tipo di un valore, usiamo la funzione “type ()”. Ciò restituisce il nome del tipo come stringa. Di seguito alcuni esempi:
type(42) -- il tipo è `number`
type("Lua Tutorial") -- il tipo è `string`
type(false) -- il tipo è `boolean`
type(var) -- il tipo è `nil` perché `var` non è definito
Imparare a programmare con Lua: cosa sono espressioni, variabili e operatori?
Un’espressione (“expression”) viene valutata dall’interprete e restituisce un valore. Le espressioni combinano letterali, operatori, variabili e chiamate di funzione. Le espressioni possono essere facoltativamente raggruppate fra parentesi “()”. Essendo un linguaggio dinamico, Lua determina automaticamente il tipo di valore restituito. Di seguito alcuni esempi di espressioni:
-- Operazioni aritmetiche in Lua
1 + 2 -- valutato al valore “3”
-- Concatenazione di stringhe in Lua
'Walther' .. 'White' – restituito come 'WaltherWhite'
-- Concatenazione di stringhe con conversione automatica di un numero in Lua
'I ' .. 3 .. ' moschettieri' -- restituito come 'I 3 moschettieri'
-- Test di uguaglianza in Lua
7 == '7' -- restituisce `false`
-- Test di uguaglianza in Lua
'piccolo' == string.lower ('PICCOLO') -- restituisce `true`
-- Informazioni di tipo dinamico
type (var) == 'nil'-- restituisce `true` perché `var` non è definito
Una variabile è un nome per un valore in memoria. Come nella maggior parte dei linguaggi di programmazione, un nome in Lua inizia con una lettera o un trattino basso (_), seguito da lettere aggiuntive, trattini bassi o numeri. Viene fatta una netta distinzione tra maiuscole e minuscole. Le seguenti parole riservate non possono essere utilizzate come nomi da sole:
“And”, “end”, “in”, “repeat”, “break”, “false”, “local”, “return”, “do”, “for”, “nil”, “then”, “else”, “function”, “not”, “true”, “elseif”, “if”, “or”, “until”, “while”
Tuttavia, queste parole riservate possono apparire come parte di un nome senza problemi:
for = "Peter" -- causa errori
for_user = "Peter" -- consentito
Un valore viene assegnato a una variabile utilizzando l’operatore di assegnazione (=), che non deve essere confuso con l’operatore di uguaglianza logica (==). Come al solito, durante l’assegnazione viene fatta una distinzione tra “L-value” e “R-value”: la variabile deve essere a sinistra dell’operatore di assegnazione, cioè è il valore L. A questo viene assegnato il valore valutato del valore R a destra:
numero = 13 -- va bene
13 = numero -- causa errori perché al numero 13 non può essere assegnato un nuovo valore
Un operatore crea un nuovo valore da uno o più operandi. Si parla di un operatore unario (una cifra) o binario (due cifre). Un operatore collega operandi di un certo tipo e restituisce un valore di un certo tipo. Diamo un’occhiata ai diversi operatori in Lua.
Gli operatori aritmetici operano sui numeri e restituiscono un numero:
Operatore aritmetico | Arietà | Operazione |
+ | binario | addizione |
- | binario | sottrazione |
* | binario | moltiplicazione |
/ | binario | divisione |
% | binario | modulo |
^ | binario | elevazione a potenza |
- | unario | negazione |
Gli operatori relazionali sono tutti binari e testano la relazione tra due operandi. Restituiscono un valore di verità:
Operatore relazionale | Test |
== | uguaglianza |
~= | disuguaglianza |
> | maggiore di |
< | minore di |
>= | maggiore o uguale a |
<= | minore o uguale a |
Gli operatori logici combinano valori di verità e restituiscono un nuovo valore di verità:
Operatore logico | Arietà | Operazione |
and | binario | collegamento E |
or | binario | collegamento O |
not | unario | negazione |
Oltre a quanto riportato sopra, ci sono due operatori speciali in Lua che servono per concatenare stringhe e determinare la potenza di un valore composto, come una tabella o una stringa:
Operatore | Arietà | Operazione |
.. | binario | concatenazione di stringhe |
# | unario | determinare il numero di elementi di una tabella / la lunghezza di una stringa |
Lua non ha operatori di assegnazione composti come “+=“ e “-=“, che sono comuni in molti linguaggi di scripting. Per aumentare e diminuire le variabili, l’operazione viene scritta esplicitamente:
prezzo = 42.99
sconto = 0.15 -- 15% di sconto
prezzo -= prezzo * sconto -- `-=` non funziona in Lua
-- La diminuzione deve essere invece scritta esplicitamente
prezzo = prezzo - (prezzo * sconto)
Comprendere gli ambiti di validità e i blocchi per il tutorial di Lua
Il concetto di “ambito di validità” è centrale in qualsiasi linguaggio di programmazione. Una variabile esiste solo entro un certo ambito di validità. Come in JavaScript, le variabili in Lua sono globali per impostazione predefinita. Tuttavia, l’uso continuo di variabili globali è noto come “anti-pattern” e dovrebbe essere evitato. In Lua, la parola chiave “local” può essere d’aiuto. Infatti, limita l’ambito di una variabile al blocco circostante, paragonabile alla dichiarazione tramite “let” in JavaScript.
-- questa variabile è globale
x = 5
-- definire variabile locale
local z = 10
I corpi delle funzioni e i cicli aprono un nuovo ambito di validità in Lua. Lua utilizza anche il concetto di blocco esplicito. Un blocco definisce un nuovo ambito di validità per il codice che si trova fra le parole chiave “do” e “end”. Ciò corrisponde alle parentesi di apertura e chiusura “{“ e ”}” in Java/C/C++. Il seguente esempio di codice mostra come sono correlati blocchi, ambiti e variabili:
-- ambito di validità esterno
do
local x = 1
do -- ambito di validità interno
local y = 2
-- generare `z` nell’ambito globale
-- accedere così alla variabile locale `x` da un ambito di validità esterno
-- così come alla variabile locale `y` dall’ambito di validità interno
z = x + y -- `z` ora ha il valore `3`
end
print(x) -- restituisce `1`
print (y) – restituisce `nil` poiché `y` non esiste nell’ambito di validità esterno
print(z) -- restituisce `3`
end
-- `z` è globale, quindi esiste al di fuori dell’ambito di validità esterno
z = z + 4
print(z) -- restituisce `7`
Imparare a programmare con le strutture di controllo di Lua
Lua utilizza le classiche strutture di controllo che troviamo anche in altri linguaggi di programmazione. Ciò include diramazioni e cicli. Di seguito un esempio delle direttive “if”, “then”, “else”, “elseif” di Lua:
limit = 42;
numero = 43;
se numero < limit then
print("sotto il limite.")
elseif numero == limit then
print("esattamente al limite…")
else
print("oltre il limite!")
end
Oltre al classico ciclo “while”, Lua riconosce anche la sua controparte “repeat”-”until”. Questa istruzione esiste anche in Ruby e richiede l’inversione della condizione applicata. Ciò significa che “while” con la condizione “numero </= limit” corrisponde a “repeat”-”until” con la condizione “numero > limit”. Attenzione all’istruzione “repeat”! Indipendentemente dalla condizione, il ciclo viene eseguito almeno una volta. Qui un esempio:
limit = 10
numero = 1
while numero <= limit do
print("Numero:", numero)
numero = numero + 1
end
-- Attenzione: anche se il `numero` è già maggiore del `limite`
-- il ciclo viene eseguito una volta
numero = 11
repeat
print("Numero:", numero)
numero = numero + 1
until numero > limit
Come la maggior parte dei linguaggi di programmazione imperativi, Lua ha anche un’istruzione “for” oltre al ciclo “while”. Possono essere utilizzate due forme: una variante simile a C con una variabile di ciclo e una variante con un iteratore. Consideriamo prima l’uso dell’istruzione “for” con una variabile di ciclo:
inizio = 1
fine = 10
for numero = inizio, fine do
print("numero corrente:", numero) -- `1,2,3,4,5,6,7,8,9,10`
end
-- impostare esplicitamente il passaggio a `2`
passaggio = 2
for numero = inizio, fine, passaggio do
print("numero corrente:", numero) -- `1,3,5,7,9`
end
-- il passaggio può anche essere negativo
passaggio = -2
-- scambiare l’inizio e la fine con un passaggio negativo per contare in ordine decrescente
for numero = fine, inizio, passaggio do
print("numero corrente:", numero) -- `10,8,6,4,2`
end
Al contrario di quello che si potrebbe pensare, la variabile definita nel ciclo “for” è locale, non globale, senza dover essere esplicitamente dichiarata come “locale” ed è qui che Lua differisce da JavaScript in modo positivo. In JavaScript, una variabile di ciclo senza “let” o “var” viene dichiarata automaticamente come globale, il che può portare a gravi errori.
Diamo adesso un’occhiata al ciclo “for” di Lua con l’iteratore. In linea di principio, l’approccio è simile a quello di Python: invece di incrementare una variabile di ciclo e usarla come indice in una lista, iteriamo direttamente sugli elementi della lista. La funzione “ipairs()” viene spesso utilizzata per generare l’iteratore. Ecco un esempio:
-- definire una lista di anni
decadi = {1910, 1920, 1930, 1940, 1950, 1960, 1970, 1980, 1990}
-- accedere ai singoli anni utilizzando un iteratore
for index, anni in ipairs(decadi) do
print(index, anno)
end
Ulteriori informazioni sulle funzioni di Lua
Come in C/C++, Java e JavaScript, le funzioni sono definite con la parola chiave “function”. Come spesso accade, i parametri delle funzioni seguono i nomi delle funzioni tra parentesi. La particolarità di Lua è che le parentesi possono essere omesse quando si richiama una funzione che ha esattamente un letterale come argomento. Una funzione di Lua non deve necessariamente restituire un valore. Secondo la definizione, in assenza di un valore si tratta di una “procedura”:
-- definire procedura
function ciao(name)
print("buongiorno", name)
end
-- richiamare una funzione
ciao("Gentile Signore")
-- è possibile anche
ciao "Gentile Signore"
-- l’opzione seguente invece non funziona
name = "Walther"
ciao name -- errore di sintassi
-- la funzione deve essere richiamata fra parentesi con una variabile invece di un letterale
ciao(name)
Se si vuole restituire un valore da una funzione, come al solito si può utilizzare la parola chiave “return”. Ciò termina l’esecuzione della funzione e restituisce il valore specificato. Nel seguente esempio di codice un numero viene moltiplicato al quadrato:
-- funzione con un unico valore di ritorno
function quadrato(numero)
-- viene valutata l’espressione `numero * numero`
-- e il suo valore restituito
return numero * numero
end
-- numero al quadrato
print(quadrato(9)) -- `81`
Come in Python e JavaScript, una funzione in Lua può accettare un numero variabile di argomenti. Gli argomenti vengono salvati nel costrutto speciale “(...)”. Per accedere agli argomenti, è spesso utile riassumerli in una lista con l’espressione “{...}”. In alternativa, può essere utilizzata la funzione “select ()”, che estrae un argomento sotto l’indice specificato. Il numero di argomenti viene determinato con l’espressione “# {...}”.
-- stampare tutti gli argomenti di una funzione
function var_args(...)
for index, arg in ipairs({...}) do
print(index, arg)
end
end
var_args('Peter', 42, true)
Oltre al numero variabile di argomenti, Lua consente anche di restituire più valori con l’istruzione “return”. Funziona in modo simile a Python, ma senza un tipo “tupla” esplicito. Come in Python, è comune assegnare i valori di ritorno a più variabili quando si chiama una funzione. Ecco un esempio:
-- funzione con diversi valori di ritorno
function primo_e_ ultimo(lista)
-- restituisce il primo e l’ultimo elemento della lista
-- i singoli valori di ritorno sono separati da una virgola `,`
return lista[1], lista[#lista]
end
persone = {"Jim", "Jack", "John"}
-- assegnazione dei valori di ritorno a più variabili
primo, ultimo = primo_e_ultimo(persone)
print("Il primo è", primo)
print("L’ultimo è", ultimo)
Se uno dei valori restituiti non è necessario, la convenzione comune consiste nell’utilizzare il trattino basso (_) come segnaposto, come mostrato nell’esempio seguente:
function min_medio_max(...)
-- impostare i valori iniziali per `min` e `max` al primo argomento
local min = select(1, ...)
local max = select(1, ...)
-- impostare inizialmente a zero il valore medio
local medio = 0
-- ripetere i numeri
-- non abbiamo bisogno della variabile index
-- utilizzare quindi `_` come segnaposto
for _, numero in ipairs({...}) do
-- impostare un nuovo minimo
if min > numero then
min = numero
end
-- impostare un nuovo massimo
if max < numero then
max = numero
end
-- sommare i numeri per la media
medio = medio + numero
end
-- dividere la somma dei numeri per il loro numero
medio = medio / #{...}
return min, medio, max
end
-- qui non abbiamo bisogno del valore `medio`
-- utilizziamo quindi `_` come segnaposto
min, _, max = min_medio_max(78, 34, 91, 7, 28)
print("il minimo e il massimo dei numeri sono", min, max)
In Lua, le funzioni sono “first-class citizens”. Ciò significa che possono essere associate a variabili e quindi trasmesse anche come argomenti ad altre funzioni. Una funzione può inoltre servire come valore di ritorno di una funzione. Lua consente una programmazione funzionale, mostrata qui usando l’esempio della famosa funzione “map ()”:
-- `map()`-funzione di Lua
-- accetta una funzione `f` e una lista come argomenti
function map(f, lista)
--creare una nuova lista per i valori di output
local _lista = {}
-- scorrere gli elementi della lista con indice
for index, valore in ipairs(lista) do
-- applicare la funzione `f()` al valore corrente della lista
-- e salvare il valore restituito in una nuova lista con lo stesso indice
_lista[index] = f(valore)
end
-- restituire una nuova lista
return _lista
end
-- lista di numeri
numeri = {3, 4, 5}
-- funzione che viene applicata a tutti gli elementi della lista
function quadrato(numero)
return numero * numero
end
-- generare i quadrati tramite la funzione `map()`
quadrati = map(quadrato, numeri) -- `{9, 16, 25}`
-- restituire i numeri al quadrato
for _, numero in ipairs(quadrati) do
print(numero)
end
La ricorsività viene spesso utilizzata nella programmazione funzionale: una funzione chiama se stessa più e più volte con argomenti modificati. Bisogna fare particolare attenzione con Lua. Le funzioni richiamate in modo ricorsivo dovrebbero essere dichiarate esplicitamente come “local”.
function f()
-- chiamata ricorsiva
f () -- si riferisce alla variabile globale `f`, se applicabile
end
-- anziché
local function f()
-- chiamata ricorsiva
f () -- si riferisce alla funzione circostante
end
-- equivalente a
local f; -- dichiarare la variabile `f` esplicitamente come `local`
f = function () -- assegnazione della funzione alla variabile locale `f`
f () -- si riferisce di sicuro alla funzione circostante
end