Linguaggi di scripting (lua)


controesempi

non sono linguaggi di scripting


caratteristiche tipiche


come nascono

origini più comuni:

gli altri linguaggi, invece:


lua

aspetti rilevanti:


i tipi

sei tipi:

nil
unico valore: nil
booleano
true o false
numero
nessuna distinzione fra intero e reale
stringa
funzione
si può mettere una funzione in una variabile
tabella
array associativo, come i dizionari Python

non si possono creare nuovi tipi


funzioni

le funzioni sono valori:


funzioni

creazione di funzione con la sintassi classica:

function esempio(a, b)
	print(a, b)
	return a + b
end

chiamata della funzione:

esempio(3, 2)

funzione in una variabile

sintassi alternativa
risultato identico

esempio = function(a, b)
	print(a, b)
	return a + b
end

come sempre l'assegnamento è in due fasi:


funzione come valore

una funzione si può creare e chiamare senza memorizzarla in una variabile

print((function(a) return a + 1 end)(3))

come funziona?


funzione come valore, come interi e stringhe

una funzione si può creare e chiamare senza memorizzarla in una variabile
così come si può fare numeri e stringhe:

print(2 + 12.54)
print("abcd")

una funzione è un valore
come 2, come 12.54 e "abcd"

questi tre valori si possono usare anche senza memorizzarli in una variabile

esempio: 2 viene usato in una somma

analogo con una funzione:
la funzione viene creata e subito usata,
senza metterla in una variabile


funzione come valore

come interi e stringhe:
la funzione viene creata e subito usata,
senza metterla in una variabile

usare una funzione = chiamarla con dei valori

print((function(a) return a + 1 end)(3))

la funzione viene creata da function(a) return a + 1 end
e subito usata con (…)(3)
è come esempio(3)
chiama la funzione passandole il valore 3


copiare una funzione

i valori si possono memorizzare in una variabile
e poi copiarli in un'altra variabile

a = 12
b = "abcd"
c = a
d = b

lo stesso si può fare con le funzioni

esempio = function(a, b)
	print(a, b)
	return a + b
end

crea la funzione e la memorizza in una variabile esempio

altro = esempio

copia il valore di esempio nella variabile altro
ora entrambe le variabili contengono la stessa funzione

esempio(3, 2)
altro(3, 2)

vengono usati i valori delle due variabili
usare una funzione = chiamarla

stessa funzione, stesso risultato


funzione passata a un'altra funzione

una funzione "normale"
si può chiamare per esempio con il valore 23

prima = function(x)
		print(x + 4)
		return x + 1
	end

una funzione può anche avere come parametro un'altra funzione

seconda = function(a, b)
		print(b)
		print(a(b))
	end

a questa funzione si può passare la prima funzione

seconda(prima, 2)

funzioni create da altre funzioni e ritornate

così come una funzione può essere passata a un'altra funzione…
così può essere ritornata

terza = function()
		temp =	function(a)
				print(a + 10)
				return a + 5
			end
		return temp
	end

crea una funzione temp

la ritorna

si può memorizzare in una variabile:

risultato = terza()

si può eseguire usando la variabile:

risultato(2)

tutto insieme

una funzione che riceve un'altra funzione
e ne ritorna un'altra ancora

quarta = function(a, b)
		print(b)
		print(a(b))
		temp =	function(a)
				print(a + 10)
				return a + 5
			end
		return temp
	end

risultato = quarta(prima, 2)
risultato(2)

lambda calcolo

un formalismo di funzioni


funzione del lambda calcolo

λvariabile . espressione

parallelo in lua:

function(x) { return espressione; }

"lambda linguaggio"

lambda calcolo visto come un linguaggio di programmazione:

niente interi, somme, stampe


esempio di formula

λx.x

analogo:

function(x) { return x; }

funzione identità: ritorna il valore passato


esempio di applicazione

λx.x λx.x

funzione identità passata a se stessa

function(x) { return x; } (function(x) { return x; }

non si capisce?


definizioni

I = λx.x

parallelo in lua:

I = function(x) { return x; }

equivalente a:

function I(x) { return x; }

non necessarie
ma chiariscono


funzione identità passata a se stessa

I = λx.x
II

in lua:

I = function(x) { return x; }
I(I)

risultato di una applicazione

I = function(x) { return x; }
I(I)

definisce la funzione I
chiama I, con argomento I stesso

valutazione: come sempre


valutazione, nel lambda calcolo

[lambda-01.fig]

funzione λx.(… x … x …)
applicata ad argomento a


argomento ⇒ variabile

[lambda-03.fig]


argomento ⇒ espressione

[lambda-05.fig]


sostituzione

[lambda-06.fig]

argomento a al posto di x


risultato

[lambda-07.fig]


espressioni ammesse


esempi di lambda-formule

λx.x
funzione identità
il risultato è uguale al suo argomento
λx.x (λx.x)
funzione identità applicata a se stessa
il risultato è la funzione identità
λs.(λx.x)
funzione con un argomento
lo ignora: il risultato è la funzione identità
λs.λz.s(z)
funzione con due argomenti
applica il primo (una funzione) al secondo
altri esempi:
vedere: A Tutorial Introduction to the Lambda Calculus, Raul Rojas

evidenziate: le "istruzioni" della funzione


esempio, in lua

λs.λz.s(z)

lua:

function(s) { return function(z) { return s(z); }; }

con le definizioni:

A = function(s) {
	B = function(z) { return s(z); }
	return B(s);
}

come un linguaggio di programmazione

unica istruzione:
chiamata di funzione

unici valori:
funzioni


completezza

si possono definire i numeri interi

si possono calcolare somme, sottrazioni, ecc.

si possono implementare condizionali e cicli

stessa capacità di calcolo degli altri linguaggi di programmazione

tecnicamente: Turing-completo
argomento successivo del corso


tabelle

le tabelle sono simili ai vettori

t = {24, 4, 8}
t[2] = -342
print(t[1], t[2], t[3])

valori di una tabella

i valori memorizzati possono essere di tipo qualsiasi
per esempio stringhe

t = {"una", "frase", "memorizzata", "in una tabella"}
print(t[1], t[2], t[3], t[4])

i valori di una tabella possono essere di tipo diverso

t = {true, 23, "abcd", nil}
print(t[1], t[2], t[3], t[4])

booleano, numero, stringa, nil


i valori di una tabella possono essere altre tabelle

q = {}
q[1] = t
print(q[1][2])

i valori di una tabella possono essere funzioni

q = {}
q[2] = function() print "ecco" end
q[2]()

indici di una tabella

spesso gli indici sono numeri

t = {24, 4, 8}
t[2] = -342
print(t[1], t[2], t[3])

ma possono anche essere stringhe

t = {}
t["efgh"] = "ijkl"
print(t["efgh"])

possono essere di tipo diverso fra loro

t = {}
t[2] = 4
t["abcd"] = 13
print(t[2], t["abcd"])

possono essere valori qualsiasi
incluse altre tabelle e funzioni


indici di una tabella

gli indici di una tabella sono valori
di tipo qualsiasi
anche stringhe

t = {}
t["efgh"] = "ijkl"
print(t["efgh"])

possono essere di tipo diverso fra loro
per esempio, un numero e una stringa

t = {}
t[2] = 4
t["abcd"] = 13
print(t[2], t["abcd"])

gli indici delle tabelle sono valori qualsiasi

esempio: una tabella che è indice di un'altra

t = {}
t[2] = 4	--- t è una tabella…
t["abcd"] = 13

r = {}
r[t] = 9	--- è indice di un'altra tabella
print(r[t][2])

altro esempio: una funzione che è indice di una tabella

s = {}
s[2] = 4
s[function() print "cosa" end] = 9

scansione di una tabella

for i,v in pairs(t)
do
	print("   ", type(i), i, v)
end

oggetti e prototipi

gli oggetti sono tabelle

esiste una sintassi alternativa nello stile orientata agli oggetti
ma per il resto gli oggetti sono tabelle normali


creare un oggetto

--- crea la tabella
p = {}

--- aggiunge dei campi: punto su un piano, con x e y
p["x"] = 12
p["y"] = 3
print(p["x"], p["y"])

--- stessa cosa, sintassi alternativa in stile oo (orientato agli oggetti)
p.x = 12
p.y = 3
print(p.x, p.y)

metodi

un metodo è un elemento della tabella

p["stampa"] =
	function(self)
		print(self.x, self.y)
	end

al posto di self si può usare qualsiasi altro nome di variabile

invocare il metodo

p.stampa(p)

stessa cosa, sintassi in stile oo

p:stampa()

metodo creato con la sintassi classica e in stile oggetti

sintassi classica:

function p.scambiati(self)
		print(self.y, self.x)
	end

sintassi in stile a oggetti

function p:scambiati()
		print(self.y, self.x)
	end

unico caso in cui la variabile si deve chiamare "self",
non va bene un altro nome

invocazione, nei due stili

p:scambiati(p)
p:scambiati()

classi

non esistono le classi

una classe sarebbe un nuovo tipo di variabili
non si possono definire nuovi tipi

si usa invece un oggetto prototipo


creazione di un nuovo oggetto

invece di crearlo da zero, si copiano i campi e i metodi di un altro


crea un secondo oggetto dal primo

q = {}
setmetatable(q, {__index = p})
q.x = -49
q.y = 123
q:stampa()

campi e metodi di un oggetto copiato

gli oggetti ereditano sia campi che metodi

r = {}
setmetatable(r, {__index = q})
r.y = 999
r:stampa()

r.x è uguale a q
r:stampa() è un metodo uguale a quello di q


sottoclassi

un oggetto si può sempre estendere
nuovi campi e nuovi metodi

esempio: alcuni punti si trovano all'interno di zone delimitate
caratteristiche aggiuntive: zona e sua stampa
cambia anche la precedente funzione di stampa

r.zona = "zona 23"
function r:printobject()
	print(self.x, self.y, self.zona)
end
function r:printzona()
	print("zona: ", self.zona)
end
r:printobject()
r:printzona()

prototipo per la sottoclasse

il nuovo oggetto, quello esteso, è il prototipo della sottoclasse
si può usare per creare altri oggetti della sottoclasse

t = {}
setmetatable(t, {__index = r})
t.x = 12
t.y = 3
t.zona = "zona 9"
t:printobject()
r:printzona()

nota sugli indici di una tabella

print(p.x)
print(p["x"])
print(p[x])

p.x significa p["x"]
p.x non significa p[x]

notare le virgolette: "x" è una stringa, x una variabile

in questo momento x non è definito
quindi vale nil
quindi p[x] significa p[nil]
è ammesso: vale sempre nil