domenica 26 aprile 2015

Lubit e i pacchetti magici.




Ho sempre odiato la definizione "mago" associato all' informatica, non c'è proprio niente di magico in una materia scientifica se non la curiosità che ci spinge a studiare e scoprire sempre nuovi argomenti... eppure in questo articolo vedremo come fare una cosa che un pò si avvicina ad una piccola magia ;)

Mettiamo di avere un PC  collegato in rete, e che questo PC sia spento quando invece ci serve che sia in funzione perché magari è un qualche tipo di server personale, come possiamo fare?

Beh la risposta più ovvia è andare fisicamente davanti al PC e accenderlo, ma questo sarebbe troppo semplice e di certo non ci sarebbe motivo di scriverci un articolo.

La piccola magia si chiama WOL (Wake on Lan) e  consiste proprio in questo, cioè avviare da remoto un PC collegato ad una rete e raggiungibile.

Un PC che supporta il WOL non è mai completamente spento ma resta costantemente in ascolto in attesa del segnale di richiesta avvio, questo segnale che ci permette di dire al PC che deve "svegliarsi" si chiama Magic Packet , si ... proprio pacchetto magico.

Come già detto di magico non c'è molto, si tratta di un particolare pacchetto ethernet a livello datalink inviato in  broadcast sulla rete LAN con il MAC address della macchina da avviare,  basta conoscere l' indirizzo MAC della scheda di rete del pc da avviare e inviargli un Magic Packet.

Per realizzare quanto detto passiamo al terminale.

Iniziamo con la configurazione del PC da "svegliare"

Prima di tutto dobbiamo assicurarci che la funzione WOL sia supportata dal pc e che sia attivata a livello BIOS, quindi andiamo nella configurazione iniziale del computer e cerchiamo la voce "Wake on lan" o "Wake on PCI" e abilitiamo .

Ora dobbiamo verificare che anche a livello di sistema operativo sia tutto attivato , quindi avviamo il PC e apriamo una finestra terminale, digitiamo :


sudo apt-get install ethtool

sudo ethtool eth0

in questo esempio ho utilizzato l'interfaccia di rete eth0, andrà bene nella maggior parte dei casi, voi potete modificarla con l'identificativo della NIC che utilizzate.

A questo punto se come output riceviamo qualcosa di simile a

......
Wake-on: g
...

vuol dire che il sistema è già predisposto per usare il WOL , se così non fosse dobbiamo attivarlo manualmente, quindi conviene creare un piccolo script da far eseguire automaticamente ad ogni avvio di Lubit digitando :


sudo leafpad /etc/init.d/wol.sh



e nell' editor :


#!/bin/bah 

sudo ethtool eth0  

exit

salviamo il file e usciamo da Leafpad.

Ora rendiamo lo script eseguibile e diciamo a Lubit di avviarlo :


sudo chmod a+x /etc/init.d/wol.sh

sudo update-rc.d wol.sh defaults



La configurazione è terminata, ora dobbiamo annotarci l'indirizzo MAC dell' interfaccia di rete :


ifconfig
e in corrispondenza dell'identificativo eth0, accanto a IndirizzoHW, troviamo il MAC , lo riconoscete perchè è composto da 6 cifre esadecimali separate da due punti , per es. 00:23:54:93:9f:fd .


Fatto questo passiamo al PC che deve inviare il Magic Packet, per farlo esistono molti programmi su qualsiasi sistema operativo, qui useremo gwakeonlan che trovate già disponibile nei repository di sistema.

Installamolo :


sudo apt-get install gwakeonlan



dopo averlo avviato clicchiamo sull'icona "+" per aggiungere i parametri del nostro PC da svegliare


digitiamo un nome di fantasia, l'indirizzo MAC che abbiamo annotato in precedenza e lasciamo il resto inalterato.

A questo punto avremo in elenco il PC appena inserito


 non ci resta che selezionarlo e cliccare su "Accendi" e ...  il gioco è fatto!



Tutto questo funziona a livello MAC, quindi sulla nostra LAN, e se invece il PC da avviare si trova su una LAN diversa?


Beh come avrete notato in fase di inserimento dei paramentri del pc da svegliare non abbiamo modificato gli ultimi  tre parametri, questi ci servono proprio per inviare il pacchetto su una rete diversa dalla nostra LAN.
Naturalmetne la rete dev'essere raggiungibile, quindi se collegata ad internet possiamo spuntare la voce "Internet" , si attiverà il campo "Sistema di destinazione" e inseriamo l'ip pubblico del PC da svegliare, mentre lasciamo inalterata la porta UDP 9.
Naturalmente  per assicurarci che il pacchetto arrivi  a destinazione dobbiamo configurare il router affinchè inoltri al PC i pacchetti UDP ricevuti sulla porta 9.

La pagina del progetto, qui

Alla prossima!

venerdì 24 aprile 2015

Corso Lua - puntata 8 - Iteratori


Iteratori predefiniti per le tabelle

Benvenuti a una nuova puntata del corso base su Lua. Il compito di oggi è spiegare come iterare le tabelle e come costruire nuovi iteratori.

Gli iteratori offrono un approccio semplice e universale per scorrere uno alla volta i valori di una collezione di dati. Vi dedicheremo una puntata proprio perché sono molto utili per scrivere codice efficiente, pulito ed elegante.
Il linguaggio Lua prevede il ciclo generic for per iterare i dati che introduce la nuova parola chiave 'in' secondo questa semantica:
for 'lista variabili' in iterator_function() do
 -- codice
end

Le tabelle di Lua sono oggetti che possono essere impiegati per rappresentare degli array oppure dei dizionari. In entrambe i casi Lua mette a disposizione due iteratori predefiniti tramite le funzioni ipairs() e pairs().

Queste funzioni restituiscono un iteratore che è a sua volta una funzione conforme alle specifiche del 'generic for'. Mentre impareremo più tardi a scrivere iteratori personalizzati, dedicheremo le prossime due sezioni a questi importanti iteratori per le tabelle.

Funzione ipairs()

Questa funzione d'iterazione restituisce a ogni ciclo due valori: l'indice dell'array e il valore corrispondente. Essa comincierà dalla posizione 1 e terminerà quando il valore alla posizione data sarà nil:
-- una tabella array
local t = {45, 56, 89, 12, 0, 2, -98}

-- iterazione tabella come array
for i, n in ipairs(t) do
    print(i, n)
end

Se la tabella contiene valori nil allora l'iteratore non raggiungerà nessuno degli elementi successivi e verranno ignorate tutte le chiavi di tipo non intero.
Il ciclo con ipairs() è equivalente a questo codice:
-- una tabella array
local t = {45, 56, 89, 12, 0, 2, -98}

local i, v = 1, t[1]
while v do
    print(i, v)
    i = i + 1
    v = t[i]
end

Se non interessa il valore dell'indice possiamo convenzionalmente utilizzare per esso il nome di variabile corrispondente a un segno di underscore che in Lua è una definizione valida:
-- una tabella array
local t = {45, 56, 89, 12, 0, 2, -98}

local sum = 0
for _, elem in ipairs(t) do
    sum = sum + elem
end
print(sum)

Se non vogliamo incorrere in errori è molto importante ricordarsi che con ipairs() verranno restituiti i valori in ordine di posizione da 1 in poi solo fino a che non verrà trovato un valore nil. Se ci troveremo in questa situazione dovremo far ricorso all'iteratore pairs() trattato alla prossima sezione.

Funzione pairs()

Questa funzione primitiva di Lua vede la tabella come dizionario pertanto l'iteratore restituirà in un ordine casuale tutte le coppie chiave valore contenute nella tabella stessa.

Una tabella con indici a salti verrà iterata parzialmente da ipairs() ma completamente da pairs():
-- produzione tabella con salto
local t = {45, 56, 89}
local i = 10
for _, v in ipairs({12, 0, 2, -98})  do
    t[i] = v
    i = i + 1
end

print("ipairs() table iteration test")
for index, elem in ipairs(t) do
    print(string.format("t[%2d] = %d", index, elem))
end

print("\npairs() table iteration test")
for key, val in pairs(t) do
    print(string.format("t[%2d] = %d", key, val))
end

Il comportamento di questi due iteratori può lasciare perplessi ma è coerente con un linguaggio di scripting.

Generic for


Come può essere implementato un iteratore in Lua?
Per iterare è necessario mantenere alcune informazioni essenziali chiamate stato dell'iteratore. Per esempio l'indice a cui siamo arrivati nell'iterazione di una tabella/array e la tabella stessa.

Perchè non utilizzare la closure per memorizzare lo stato dell'iteratore?

Abbiamo incontrato le closure nella puntata 6 del corso base su Lua, post che vi invito a rileggere se non vi ricordate o non sapete di cosa si tratta. E allora proviamo a scrivere il codice per iterare una tabella:
-- costructor
local t = {45, 87, 98, 10, 16}

function iter(t)
    local i = 0
    return function ()
        i = i + 1
        return t[i]
    end
end

-- utilizzo
local iter_test = iter(t)
while true do
    local val = iter_test()
    if val == nil then
        break
    end
    print(val)
end

Ok. Funziona, molto semplicemente. Non è stato necessario introdurre nessun nuovo elemento al linguaggio. L'iteratore è solamente una questione d'implementazione che tra l'altro ricrea l'iteratore ipairs() visto poco fa.

Infatti, la funzione iter_test() mantiene nella closure lo stato dell'iteratore e restituisce uno dopo l'altro gli elementi della tabella. Il ciclo infinito --- il while loop --- s'interrompe quando il valore è nil.

Tuttavia data l'importanza degli iteratori Lua introduce un nuovo costrutto chiamato 'generic for' che si aspetta una funzione proprio come la iter() del codice precedente ed effettua i controlli necessari. Vediamo se funziona:
-- costructor
local t = {45, 87, 98, 10, 16}

function iter(t)
    local i = 0
    return function ()
        i = i + 1
        return t[i]
    end
end

-- utilizzo con il generic for
for v in iter(t) do
    print(v)
end

Riassumendo, la costruzione di un iteratore in Lua si basa sulla creazione di una funzione che restituisce uno alla volta gli elementi dell’insieme nella sequenza desiderata. Una volta costruito l’iteratore, questo potrà essere impiegato in un ciclo che in Lua viene chiamato 'generic for'.

Se per esempio si volesse iterare la collezione dei numeri pari compresi nell’intervallo da 1 a 10, avendo a disposizione l’apposito iteratore evenNum(first, last) che definiremo in seguito, potrei scrivere semplicemente:
for n in evenNum(1,10) do
   print(n)
end

L'esempio dei numeri pari

Per definire questo iteratore dobbiamo creare una funzione che restituisce a sua volta una funzione in grado di generare la sequenza dei numeri pari. L’iterazione termina quando giunti all’ultimo elemento, la funzione restituirà il valore nullo ovvero ‘nil’, cosa che succede in automatico senza dover esplicitare un’istruzione di return grazie al funzionamento del 'generic for'.

Potremo fare così: dato il numero iniziale per prima cosa potremo calcolare il numero pari successivo usando la funzione della libreria standard di Lua math.ceil() che fornisce il numero arrotondato al primo intero superiore dell’argomento.
Poi potremo creare la funzione di iterazione in sintassi anonima che prima incrementa di 2 il numero pari precedente --- ed ecco perché dovremo inizialmente sottrarre la stessa quantità all’indice --- e, se questo è inferiore all’estremo superiore dell’intervallo ritornerà l’indice e il numero pari della sequenza. Ecco il codice completo:
-- iteratore dei numeri pari compresi
-- nell'intervallo [first, last]
function evenNum(first, last)
   -- primo numero pari della sequenza
   local val = 2*math.ceil(first/2) - 2
   local i = 0
   return function ()
             i = i + 1
             val = val + 2
             if val<=last then
                return i, val -- due variabili di ciclo
             end
          end
end

-- ciclo con due variabili di ciclo
for idx, val in evenNum(13,20) do
    print(idx, val)
end

In questo esempio, oltre ad approfondire il concetto di iterazione basata sulla closure di Lua, possiamo notare che il 'generic for' effettua correttamente anche l'assegnazione a più variabili di ciclo con regole viste nella puntata del corso in cui abbiamo parlato dell'argomento (la puntata 2).

Naturalmente, l’implementazione data di evenNum() è solo una delle possibili soluzioni, e non è detto che non debbano essere considerate situazioni particolari come quella in cui si passa all’iteratore un solo numero o addirittura nessun argomento.

Stateless iterator


Una seconda versione del generatore di numeri pari può essere un buon esempio di un iteratore in Lua che non necessita di una closure per un risultato ancora più efficiente.

Per capire come ciò sia possibile dobbiamo conoscere nel dettaglio come funziona il 'generic for' in Lua; dopo la parola chiave 'in' esso si aspetta tre parametri: la funzione dell’iteratore da chiamare a ogni ciclo, una variabile che rappresenta lo stato invariante e la variabile di controllo.

Nel seguente codice la funzione evenNum() provvede a restituire i tre parametri necessari: la funzione nextEven() come iteratore, lo stato invariante che per noi è il numero a cui la sequenza dovrà fermarsi e la variabile di controllo che è proprio il valore nella sequenza dei numeri pari, e con ciò abbiamo realizzato un stateless iterator in Lua.
La funzione iteratrice nextEven() verrà chiamata a ogni ciclo con nell’ordine lo stato invariante e la variabile di controllo, pertanto fate attenzione, dovete mettere in questo stesso ordine gli argomenti nella definizione:
-- even numbers stateless iterator
local function nextEven(last, i)
   i = i + 2
   if i<=last then
      return i
   end
end
 
local function evenNum(a, b)
   a = 2*math.ceil(a/2)
   return nextEven, b, a-2
end
 
-- example of the 'generic for' cycle
for n in evenNum(10, 20) do
    print(n)
end

E ora gli esercizi...

1 - Dopo aver definito una tabella con chiavi e valori stampare le singole coppie tramite l'iteratore predefinito 'pairs()'.

2 - Scrivere una funzione che accetta una tabella/array di stringhe e utilizzare la funzione di libreria 'string.upper()' per restituire una tabella/array con il testo trasformato in maiuscolo (per esempio con '{"abc", "def", "ghi"}' in '{"ABC", "DEF", "GHI"}').

3 - Scrivere la funzione/closure per l'iteratore che restituisce la sequenza dei quadrati dei numeri naturali a partire da 1 fino a un valore dato.

4 - Scrivere la versione 'stateless' per l'iteratore dell'esercizio precedente.

Riassunto della puntata

Con gli iteratori abbiamo terminato l'esplorazione di base del linguaggio Lua.
Queste prime otto puntate pubblicate sul blog di Luigi sono sufficienti per scrivere programmi utili in Lua perché trattano con essenzialità di tutti gli argomenti necessari.

Il programma del corso prosegue con la prossima puntata che sarà dedicata alla programmazione a oggetti in Lua. State pronti.
R.

sabato 18 aprile 2015

Corso Lua - puntata 7 - La libreria standard


La libreria standard di Lua


Eccoci giunti al grande capitolo della libreria standard di Lua. Si tratta di una collezione di funzioni utili a svolgere compiti ricorrenti su stringhe, file, tabelle, ecc, e si trovano precaricate in una serie di tabelle e quindi immediatamente utilizzabili.
L'elenco completo ma in ordine sparso con il nome della tabella/modulo contenitore e la descrizione applicativa è il seguente:
  • math: matematica;
  • table: utilità sulle tabelle;
  • string: ricerca, sostituzione e pattern matching;
  • io: input/output facility, operazioni sui file;
  • bit32: operazioni bitwise (solo in Lua 5.2)
  • os: date e chiamate di sistema;
  • coroutine: creazione e controllo delle coroutine;
  • utf8: utilità per testo in codifica Unicode UTF-8 (da Lua 5.3)
  • package: caricamento di librerie esterne;
  • debug: accesso alle variabili e performance assessment.

La pagina web a questo indirizzo fornisce tutte le informazioni di dettaglio sulla libreria standard di Lua 5.2.

Libreria matematica

Nella libreria memorizzata nella tabella 'math' ci sono le funzioni trigonometriche 'sin', 'cos', 'tan', 'asin' eccetera --- che come di consueto lavorano in radianti, le funzioni esponenziali 'exp', 'log', 'log10', quelle di arrotondamento 'ceil', 'floor' e quelle per la generazione pseudocasuale di numeri come 'random', 'randomseed', eccetera. Oltre alle funzioni citate la tabella include campi numerici come la costante π.

Un esempio introduttivo:
print(math.pi)
print(math.sin( math.pi/2 ))
print(math.cos(0))

-- accorciamo i nomi delle funzioni ;-)
local pi, sin, cos = math.pi, math.sin, math.cos
local square = function (x) return x*x end
local function one(a)
    return square(sin(a))+square(cos(a))
end

for i=0, 1, 0.1 do
    print(i, one(i))
end

Notate come nella funzione one() entra in azione la closure --- introdotta nella puntata precedente --- perché in essa si fa uso di funzioni definite nell'ambiente esterno rispetto al corpo della funzione stessa. Ciò conferma quanto sia naturale utilizzare questa potente caratteristica di Lua anche con riferimenti a funzione.

Libreria stringhe

In Lua non è infrequente elaborare grandi porzioni di testo.
La libreria per le stringhe è memorizzata nella tabella 'string' ed è una delle più utili.
Con essa si possono formattare campi e compiere operazioni di ricerca e sostituzione.

string.format()

La funzione più semplice è quella di formattazione string.format(). Essa restituisce una stringa prodotta con il formato fornito come primo argomento riempito con i dati forniti dal secondo argomento in poi.

Il formato è esso stesso specificato come una stringa contenente dei campi creati con il simbolo percentuale e uno specificatore di tipo. Per esempio "%d" indica il formato relativo a un numero intero, dove 'd' sta per digit mentre "%f" indica il segnaposto per un numero decimale --- 'f' sta per float.

I campi formato derivano da quelli della funzione classica di libreria printf() del C. Di seguito un codice di esempio:

-- "%d" means digits
local s1 = string.format("%d", 456.18)
print(s1)
local s01 = string.format("%05d", 456.18)
print(s01)

-- "%f" means float
local num = 123.456
local s2 = string.format("intero: %d decimale: %0.2f", num, num)

-- "%s" means string
local s3 = string.format("s1='%s', s2='%s'", s1, s2)
print(s3)

local s4 = string.format("%24s", "pippo")
print(s4)

Come avete potuto notare nel codice di esempio, è anche possibile fornire un ulteriore specifica di dettaglio tra il '%' e lo specificatore di formato, per esempio per gestire il numero delle cifre decimali.

Pattern

Per elaborare il testo si utilizza di solito una libreria per le espressioni regolari. Lua mette a disposizione alcune funzioni di sostituzione e "pattern matching" meno complete dell'implementazione dello standard POSIX per le espressioni regolari ma molto spesso più semplici da utilizzare.

Esistono due strumenti di base, il primo è il pattern e il secondo è la capture.

Il pattern è una stringa che può contenere campi chiamati classi simili a quelli per la funzione di formato vista in precedenza che stavolta però si riferiscono al singolo carattere, e questa differenza è essenziale.

La funzione di base che accetta pattern è string.match() che restituisce la prima corrispondenza trovata nella stringa primo argomento che corrisponde al pattern dato come secondo argomento.

Possiamo ricercare in un numero di tre cifre all'interno di un testo con il pattern "%d%d%d":
-- semplice pattern in azione
local s = "le prime tre cifre decimali di π = 3,141592654 sono"
local pattern = "%d%d%d"
print(string.match(s, pattern))

Le classi carattere possibili sono le seguenti:
  • . un carattere qualsiasi;
  • %a una lettera;
  • %c un carattere di controllo;
  • %d una cifra;
  • %l una lettera minuscola;
  • %u una lettera maiuscola;
  • %p un carattere di interpunzione;
  • %s un carattere spazio;
  • %w un carattere alfanumerico;
  • %x un carattere esadecimale;
  • %z il carattere rappresentato con il codice 0.

Le classi ammettono quattro modificatori per esprimere le ripetizioni dei caratteri:
  • '+' indica 1 o più ripetizioni;
  • '*' indica 0 o più ripetizioni;
  • '-' come '*' ma nella sequenza più breve;
  • '?' indica 0 o 1 occorrenza;

-- occorrenza di un numero intero
-- come una o più cifre consecutive
print(string.match("l'intero 65 interno", "%d+"))
print(string.match("l'intero 0065 interno", "%d+"))

-- e per estrarre un numero decimale?
-- il punto è una classe così dobbiamo utilizzare
-- la classe %. per ricercare il carattere '.'
print(string.match("num = 45.12 :-)", "%d+%.%d+"))

Capture

Il pattern può essere arricchito per non solo trovare corrispondenze ma per restituirne parti componenti. Questa funzionalità viene chiamata capture e consiste semplicemente nel racchiudere nel pattern tra parentesi tonde le sequenze di caratteri ricercati.

Per esempio per estrarre l'anno di una data nel formato 'dd/mm/yyyy' possiamo usare il pattern con la capture seguente "%d%d/%d%d/(%d%d%d%d)":
-- extract only

local s = "This '10/03/2025' is a future date"
print(string.match(s, "%d%d/%d%d/(%d%d%d%d)"))

Più capture nel pattern daranno altrettanti argomenti multipli di uscita:
-- extract all
local s = "This '10/03/2025' is a future date"
local d, m, y = string.match(s, "(%d+%d?)/(%d%d)/(%d%d%d%d)")
print(d, m, y)

string.gsub()

Abbiamo appena cominciato a scoprire le funzionalità dedicate al testo disponibili nella libreria standard di Lua precaricata nell'interprete.

Diamo solo un altro sguardo alla libreria presentando la funzione string.gsub(). Il suo nome sta per global substitution, ovvero sostituisce tutte le occorrenze in un testo.

Intanto per individuare le occorrenze è naturale pensare di utilizzare un pattern e che sia possibile utilizzare le capture nel testo di sostituzione, per esempio:

local s = "The house is black."
print(string.gsub(s, "black", "red"))
print(string.gsub(s, "(%a)lac(%a)", "%2lac%1"))

Il primo argomento è la stringa da ricercare, il secondo è il pattern e il terzo è il testo di sostituzione dell'occorrenza ma può essere una tabella dove le chiavi saranno corrispondenti al pattern saranno sostituite con i rispettivi valori, oppure anche una funzione che riceverà le catture e calcolerà il testo da sostituire.

Una funzione quindi assai flessibile. Mi viene in mente questo esercizio: moltiplicare di 10 tutti gli interi in una stringa, ed ecco il codice:

local s = "Cose da fare oggi 5, cosa da fare domani 2"
print(string.gsub(s, "%d+", function(n)
    return tonumber(n)*10
end))

A questo punto degli esempi avrete certamente capito che 'gsub()' restituisce anche il numero delle sostituzioni effettuate.

Tutte queste funzioni restituiscono una stringa costruita ex-novo e non modificano la stringa originale di ricerca.

Esercizi


1 - Qual è la differenza tra i campi di formato della funzione string.format() e le classi dei pattern? Quali le somiglianze?

2 - Stampare una data nel formato 'dd/mm/yyyy' a partire dagli interi contenuti nelle variabili d, m e y.

3 - Cosa restituisce l'esecuzione della funzione
string.match("num = .123456 :-)", "%d+%.%d+")

Quale pattern corrisponde a un numero decimale la cui parte intera può essere omessa?

4 - Come estrarre dal nome di un file l'estensione?

5 - Come eliminare da un testo eventuali caratteri spazio iniziali e/o finali?

6 - Il pattern "(%d+)/(%d+)/(%d+)" è adatto per catturare giorno, mese e anno di una data presente in una stringa nel formato 'dd/mm/yyyy'?

7 - Creare un esempio che utilizzi la funzione string.gsub() e una funzione in sintassi anonima a due argomenti corrispondenti a due capture nel pattern di ricerca.


Riassunto della puntata

In Lua sono immediatamente disponibili un bel gruppo di funzioni che ne formano la libreria standard. In questa puntata abbiamo esplorato in particolare la libreria dedicata al testo lasciando all'utente di completare l'argomento sui testi di riferimento del linguaggio.

La prossima puntata sarà dedicata al generic for il costrutto di Lua per iterare collezioni di dati.

venerdì 10 aprile 2015

Corso Lua - puntata 6 - Le funzioni


Funzioni

Le funzioni in Lua sono il principale mezzo di astrazione e lo strumento base per rendere il codice strutturato. Nelle prime puntate di questo corso base su Lua ne avete sentito la mancanza, dite la verità!

Coerentemente con il resto del linguaggio la sintassi di una funzione comprende due parole chiave che servono per delimitare il blocco di codice contenuto in essa: 'function' ed 'end'. Una funzione può accettare argomenti e può restituire dati tramite la parola chiave 'return'.

Come primo esempio, vi presento una funzione per calcolare l'ennesimo numero della serie di Fibonacci. Un elemento si ottiene sommando i precedenti due elementi avendo posto uguale a 1 i primi due:
function fibonacci(n)
     if n < 2 then
          return 1
     end
     
     local n1, n2 = 1, 1
     for i = 1, n-1 do
          n1, n2 = n2, n1 + n2 -- assegnazione multipla
     end
     return n1
end

print(fibonacci(10)) --> 55

Con le regole dell'assegnazione multipla una funzione può accettare più argomenti. Se essa verrà chiamata con più argomenti rispetto a quelli che essa prevede quelli in eccesso verranno ignorati mentre, viceversa, se gli argomenti sono inferiori a quelli previsti allora a quelli mancanti verrà assegnato il valore nil.
Ma questo vale anche per i dati di ritorno quando la funzione è usata come espressione in un'istruzione di assegnamento. Basta inserire dopo l'istruzione return la lista delle espressioni separate da virgola che saranno valutate e assegnate alle corrispondenti variabili.

Per esempio, potremo modificare la funzione precedente per restituire la somma dei primi n numeri di Fibonacci oltre che solamente l'ennesimo elemento della serie stessa e considerare un valore di default se l'argomento è nil:

function fibonacci(n)
     n = n or 10 -- the default value is 10
     if n == 1 then
          return 1, 1
     end
     
     if n == 2 then
          return 1, 2
     end
     
     local sum = 1
     local n1, n2 = 1, 1
     for i = 1, n-1 do
          n1, n2 = n2, n1 + n2
          sum = sum + n1
     end
     return n1, sum
end

local fib_10, sum_fib_10 = fibonacci()
print(fib_10, sum_fib_10)

Funzioni: valori di prima classe, I

In Lua le funzioni sono un tipo. Possono essere assegnate a una variabile e passate come argomento a una funzione.

Questa proprietà non si trova spesso nei linguaggi di scripting e offre una nuova flessibilità al codice.

Tutte le funzioni sono memorizzate in variabili. Per assegnare direttamente una funzione a una variabile esiste in Lua la sintassi anonima:
local add = function (a, b)
    return a + b
end

print(add(45.4564, 161.486))

Essendo le funzioni valori di prima classe ne consegue che in Lua le funzioni sono oggetti senza nome esattamente come lo sono tipi come i numeri e le stringhe. Inoltre, la sintassi classica di definizione
function variable_name (args)
    -- function body
end
è solo zucchero sintattico perché l'interprete Lua la tradurrà effettivamente e automaticamente nel codice equivalente
variable_name = function (args)
    -- function body
end

Funzioni: valori di prima classe, II


Un esempio di funzione con un argomento funzione è il seguente, una funzione esegue un numero di volte dato la stessa funzione priva di argomenti:
function print_five()
    print(5)
end

function do_many(fn, n)
    for i=1, n or 1 do
        fn()
    end
end

do_many(print_five)
do_many(print_five, 10)

do_many(function () print("---") end, 12)

Molto interessante, anzi senza dubbio fantastico. Nell'ultima riga di codice l'argomento è una funzione definita in sintassi anonima che verrà eseguita 12 volte.

Per prendere confidenza con il concetto di funzioni come valori di prima classe, cambiamo il significato della funzione precostruita in Lua 'print()'. Ecco come:
local println = print
print = function (n)
    println("Argomento funzione -> "..n)
end

print(12)

Tabelle e funzioni

Se una tabella può contenere chiavi con qualsiasi valore allora può contenere anche funzioni!
Le sintassi sono queste --- esplicitate con il codice riportato di seguito:
  1. assegnare la variabile di funzione a una chiave di tabella;
  2. assegnare direttamente la chiave di tabella con la definizione di funzione in sintassi anonima;
  3. usare il costruttore di tabelle per assegnare funzioni in sintassi anonima.

-- primo caso
local function tipo_i()
    -- body
end

local t = {}
t.func_1 = tipo_i

-- secondo caso
local t = {}
t.func_2 = function ()
    -- body
end

-- terzo caso con più di una funzione
local t = {
    func_3_i = function ()
        -- body
    end,
    
    func_3_ii = function ()
        -- body
    end,
    
    func_3_iii = function ()
        -- body
    end,
}

Con questo meccanismo una tabella può svolgere il ruolo di modulo memorizzando funzioni utili a un certo scopo e in effetti la libreria standard di Lua si presenta all'utente proprio in questo modo.

Variadic arguments

Una funzione può ricevere un numero variabile di argomenti rappresentati da tre dot consecutivi '...'. Nel corpo della funzione i tre punti rappresenteranno la lista degli argomenti, dunque possiamo o costruire con essi una tabella oppure effettuare un'assegnazione multipla.

Un esempio è una funzione che restituisce la somma di tutti gli argomenti numerici:
-- per un massimo di 3 argomenti
local function add_three(...)
    local n1, n2, n3 = ...
    return (n1 or 0) + (n2 or 0) + (n3 or 0)
end

-- con tutti gli argomenti
local function add_all(...)
    local t = {...} -- collecting args in a table
    local sum = 0
    for i = 1, #t do
        sum = sum + t[i]
    end
    return sum
end

print(add_three(40, 20))
print(add_all(45, 48, 5456))
print(add_three(14, 15), add_all(-89, 45.6))

Per inciso, anche la funzione base 'print()' accetta un numero variabile di argomenti.
Il meccanismo è ancora più flessibile perché tra i primi argomenti vi possono essere variabili "fisse".
Per esempio il primo parametro potrebbe essere un moltiplicatore:
local function add_and_multiply(molt, ...)
    local t = {...}
    local sum = 0
    for i = 1, #t do
        sum = sum + t[i]
    end
    
    return molt * sum
end

print(add_and_multiply(10, 45.23, 48, 9.36, -8, -56.3))

Un'altra funzione predefinita 'select()' consente di accedere alla lista degli argomenti in dettaglio. Infatti se tra gli argomenti compare un valore nil avremo problemi ad accedere ai paraemetri successivi nel codice precedente perché --- come sappiamo già --- l'operatore di lunghezza # considera il nil come valore sentinella di fine array/tabella.

Il selettore prevede un primo parametro fisso e la lista variabile inserita con i tre punti '...'. Se questo parametro è un intero allora questo verrà utilizzato come indice per restituire il corrispondente parametro. Se invece il parametro è la stringa "#" restitisce il numero di argomenti extra presenti dopo l'eleventuale nil intermedio.
Il codice seguente preso pari pari dal PIL --- certamente il punto di riferimento principale su Lua scritto dallo stesso Autore del linguaggio Roberto Ierusalimschy, tra l'altro composto in LaTeX e venduto come contributo al progetto Lua stesso:
for i = 1, select("#", ...) do
    local arg = select(i, ...)
    -- loop body
end

Omettere le parentesi tonde se...

In Lua esiste la sintassi di chiamata a funzione semplificata, ammessa opzionalmente solo se:
  • la funzione accetta un unico argomento di tipo stringa;
  • la funzione accetta un unico argomento di tipo tabella.
e consiste nella possibilità di ommettere le parentesi tonde ().
Per esempio:

print "si è possibile anche questo..."

local function is_empty(t)
    if #t == 0 then
        return true
    else
        return false
    end
end

-- questo:
print(is_empty{})
print(is_empty{1, 2, 3})

-- invece di questo (sempre possibile):
print(is_empty({}))
print(is_empty({1, 2, 3}))

Closure

Chiudiamo la puntata con uno strano termine forse meglio noto agli sviluppatori dei linguaggi funzionali: la closure.

Questa proprietà di Lua amplia il concetto di funzione rendendo possibile l'accesso dall'interno di essa ai dati presenti nel contesto esterno. Ciò è possibile perché alla chiamata di una funzione viene creato uno spazio di memoria del contesto esterno unico e indipendente.

Tutte le chiamate a una stessa funzione condivideranno una stessa closure.

Se questo è vero una funzione potrebbe incrementare un contatore creato al suo interno, e anche qui prendo l'esempio di codice dal PIL:
local function new_counter()
    local i = 0 -- variabile nel contesto esterno
    return function ()
        i = i + 1 -- accesso alla closure
        return i
    end
end

local c1 = new_counter()
print(c1()) --> 1
print(c1()) --> 2
print(c1()) --> 3
print(c1()) --> 4
print(c1()) --> 5

local c2 = new_counter()
print(c2()) --> 1
print(c2()) --> 2
print(c2()) --> 3

print(c1()) --> 6

A parole il codice definisce una funzione 'new_counter()' che restituisce una funzione che ha accesso indipendente al contesto (la variabile 'i').
Come si verifica dalle righe successive a ogni chiamata della funzione contatore il valore catturato è unico e indipendente.

Tecnicamente la closure è la funzione effettiva mentre invece la funzione non è altro che il prototipo della closure.

Le closure consentono di implementare diverse tecniche utili in modo naturale e concettualmente semplice. Una funzione di ordinamento potrebbe per esempio accettare come parametro una funzione di confronto per stabilire l'ordine tra due elementi tramite l'accesso a una seconda tabella esterna contenente informazioni utili per l'ordinamento stesso.

Nel prossimo esempio mettiamo in pratica l'idea appena presentata. Il codice utilizza una funzione della libreria di Lua, che introdurremo nella prossima puntata, in particolare table.sort(), per applicare l'algoritmo di ordinamento alla tabella passata come argomento in base al criterio di ordine stabilito con la funzione passata come secondo argomento in sintassi anonima.

local years = {1994, 1996, 1998, 2000, 2002}
local val = {
    [1994] = 12.5,
    [1996] = 10.2,
    [1998] = 10.9,
    [2000] =  8.9,
    [2002] = 12.9,
}

local function sort_by_value(tab)
    table.sort(tab,
        function (a, b)
            return val[a] > val[b]
        end
    )
end

sort_by_value(years)

for i = 1, #years do
    print(years[i])
end

Esercizi


1 - Scrivere una funzione che sulla base della stringa in ingresso "+", "-", "*", "/" restituisca la funzione corrispondente per due argomenti.

2 - Scrivere la funzione che accetti due argomenti numerici e ne restituisca i risultati delle quattro operazioni aritmetiche.

3 - Scrivere una funzione che restituisca il fattoriale di un numero memorizzandone in una tabella di closure i risultati per evitare di ripetere il calcolo in chiamate successive con pari argomento.

4 - Scrivere una funzione con un argomento opzionale rispetto al primo parametro numerico che ne restituisca il seno interpretandolo in radianti se l'argomento opzionale è nil oppure "rad", in gradi sessadecimali se "deg" o in gradi centesimali se "grd".

5 - Scrivere una funzione che accetti come primo argomento una funzione f: R -> R (prende un numero e restituisce un numero), come secondo e terzo argomento i due valori dell'intervallo di calcolo e come quarto argomento il numero di punti in cui suddividere l'intervallo. La funzione dovrà stampare i valori che la funzione argomento assume nei punti definiti.

Riassunto della puntata

Una puntata bella densa dedicata alle funzioni di Lua caratterizzate dall'essere valori di prima classe --- o anche valori higher-order --- e dalle closures. Niente male per un linguaggio di scripting ;-)