[#_lesson_5_your_own_functions] == Lezione 5: Funzioni tutte tue image::learn/05-functions.png[La lezione finita, 480] La quarta lezione è finita con un avvertimento sul tuo stesso programma: `onOverlayUpdate` calcola dove vanno centoventi stelle, decide quanto è luminosa ognuna, le disegna e conta la discesa, ed è una sola funzione che fa quattro lavori. Le stanno per chiedere di farne un quinto, perché un campo di stelle senza niente davanti non è un gioco. In questa lezione la smonti. Scriverai funzioni tutte tue -- funzioni di cui Singe non ha mai sentito parlare e che non chiamerà mai -- e passerai a loro il lavoro. Alla fine tutto il campo di stelle è una riga che dice `drawStars()`, in fondo allo schermo è tornata un'astronave, e il programma è più facile da leggere di quando faceva meno. [#_start_a_new_folder_4] === Crea una nuova cartella Una cartella chiamata `functions`, dentro un file chiamato `functions.singe`, e `Singe -R functions` in esecuzione in un terminale. Copiaci dentro il programma finito della quarta lezione. Eccolo, invariato, e vale la pena eseguirlo una volta prima di toccare qualsiasi cosa: [source,lua] ---- LAYERS = 3 STARS_PER_LAYER = 40 ACROSS_STEP = 37 DOWN_STEP = 61 LAYER_SHIFT = 13 LAYER_SPEED = 0.5 LAYER_BRIGHT = 55 width = overlayGetWidth() height = overlayGetHeight() drift = 0 function onOverlayUpdate() drift = drift + 1 overlayClear() for layer = 1, LAYERS do for star = 1, STARS_PER_LAYER do starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height shade = math.random(60, 90) + layer * LAYER_BRIGHT colorForeground(shade, shade, shade) overlayPlot(starX, starY) end end return OVERLAY_UPDATED end ---- Tre strati di stelle che scendono lungo la finestra a tre velocità. Niente nella prima metà di questa lezione cambia quello che vedi. Tutto quello che c'è cambia il modo in cui si legge il programma. [#_one_job_one_name] === Un lavoro, un nome Comincia dalle due righe che mettono una stella sullo schermo. Sopra `onOverlayUpdate`, aggiungi questo: [source,lua] ---- function drawStar(x, y, shade) colorForeground(shade, shade, shade) overlayPlot(x, y) end ---- Poi, nel ciclo interno, cancella quelle due righe e al loro posto chiama la funzione: [source,lua] ---- for star = 1, STARS_PER_LAYER do starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height shade = math.random(60, 90) + layer * LAYER_BRIGHT drawStar(starX, starY, shade) end ---- Salva. L'immagine è la stessa. Hai già scritto delle funzioni -- `onOverlayUpdate` è una di queste, e lo sono anche `onInputPressed` e `onInputReleased` della terza lezione -- ma quelle sono callback. Tu le scrivi e Singe le chiama. `drawStar` è diversa. Singe non ne ha mai sentito parlare. Viene eseguita perché *tu* hai scritto il suo nome e le parentesi, nello stesso modo in cui chiami `overlayPlot`, e niente al mondo la eseguirà se non lo fai tu. Il trucco è tutto qui. D'ora in poi, quando ti trovi a pensare «e adesso disegna una stella», puoi scrivere «disegna una stella» e intenderlo davvero. [#_a_parameter_is_a_name_not_a_thing] === Un parametro è un nome, non una cosa Guarda di nuovo la prima riga di `drawStar`. [source,lua] ---- function drawStar(x, y, shade) ---- I tre nomi tra le parentesi sono *parametri*. Un parametro è un nome che la funzione usa per qualunque cosa le abbia passato chi la chiama. Quando scrivi `drawStar(starX, starY, shade)`, Lua mette in `x` quello che c'è in `starX`, in `y` quello che c'è in `starY`, in `shade` quello che c'è in `shade`, e poi esegue la funzione. I nomi non hanno niente a che fare tra loro. Il numero di chi chiama là fuori si chiama `starX` e qui dentro `x`, e funzionerebbe esattamente allo stesso modo se lo chiamassi `across` o `banana`. Dentro la funzione esistono solo i nomi tra le parentesi, e contengono copie di quello che è entrato. È qui che le parentesi vuote della prima lezione finalmente hanno senso. [source,lua] ---- function onOverlayUpdate() ---- Niente tra le parentesi vuol dire nessun parametro: il motore non ha niente da dire a questa funzione, quindi non c'è niente a cui dare un nome. Confrontala con la callback che hai scritto nella terza lezione. [source,lua] ---- function onInputPressed(what) ---- Un parametro, chiamato `what`. A questa il motore ha qualcosa da dire -- quale comando è stato premuto -- quindi c'è un nome in cui riceverlo. Usi un parametro dalla terza lezione. Un parametro è davvero la copia personale della funzione, e vale la pena dimostrartelo una volta. Scrivi questa, chiamala da `onOverlayUpdate` e poi toglila di nuovo: [source,lua] ---- function half(number) number = number // 2 return number end ---- Chiamala come `half(drift)`. La funzione dimezza il suo `number` e restituisce la risposta, e `drift`, là fuori in `onOverlayUpdate`, continua a contare come se niente fosse. Una funzione non può allungare la mano e cambiare le variabili di chi la chiama attraverso un parametro. Riceve una copia, e quello che fa alla copia sono affari suoi. [#_handing_an_answer_back] === Restituire una risposta `half` ha fatto una cosa che le funzioni scritte da te finora non facevano: ha dato una risposta. È l'ultima riga. [source,lua] ---- return number ---- `return` restituisce un valore a chi ha chiamato la funzione, e la chiamata si trasforma in quel valore. Scrivi `smaller = half(drift)` e Lua esegue `half`, prende quello che `half` ha restituito e lo mette in `smaller`. Hai incontrato `return` nella prima lezione, dove `onOverlayUpdate` restituisce `OVERLAY_UPDATED` per rispondere alla domanda di Singe se fosse cambiato qualcosa. Qui è la stessa parola che fa lo stesso lavoro, solo che adesso la domanda la fai tu. La riga della luminosità implora di diventare una funzione con una risposta. Aggiungi questa: [source,lua] ---- function shadeFor(layer) return math.random(60, 90) + layer * LAYER_BRIGHT end ---- Tutto il corpo è un `return`, cosa normale per una funzione piccola. Ora il ciclo interno può passare la risposta direttamente a `drawStar`, senza un nome in mezzo: [source,lua] ---- drawStar(starX, starY, shadeFor(layer)) ---- Una chiamata dentro una chiamata. Lua calcola prima quella interna, riceve un numero da `shadeFor` e passa quel numero a `drawStar` come terzo argomento. Hai già scritto qualcosa di simile senza accorgertene: il `math.floor(drift * layer * LAYER_SPEED)` della quarta lezione è una chiamata la cui risposta viene usata sul momento, in mezzo a un calcolo più grande. [#_two_answers_at_once] === Due risposte insieme Le due righe della posizione sono più difficili, perché sono due numeri calcolati insieme e dovresti sceglierne uno da restituire. Nella maggior parte dei linguaggi una funzione può restituire un solo valore, e tirarne fuori due è una seccatura. Lua ti lascia restituirne quanti ne vuoi. [source,lua] ---- function starAt(star, layer) local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height return x, y end ---- Raccogli le risposte mettendo due nomi a sinistra, separati da una virgola: [source,lua] ---- local x, y = starAt(star, layer) ---- Il primo valore nel primo nome, il secondo nel secondo. Se chiedi meno nomi di quanti valori restituisce la funzione, i valori in più vengono buttati via: `local x = starAt(star, layer)` ti dà il numero orizzontale e perde quello verticale, senza lamentarsi. Se chiedi più nomi di quanti sono i valori, quelli in più sono `nil`. Ignora la parola `local` in quei due blocchi ancora per un minuto. Sta arrivando una sezione tutta sua, ed è quella importante. [#_the_star_field_in_one_line] === Il campo di stelle in una riga Il ciclo interno ora è tre righe brevi, tutte su uno strato di stelle. Questo è un lavoro con un nome. [source,lua] ---- function drawLayer(layer) for star = 1, STARS_PER_LAYER do local x, y = starAt(star, layer) drawStar(x, y, shadeFor(layer)) end end ---- E anche il ciclo esterno è un lavoro con un nome: [source,lua] ---- function drawStars() for layer = 1, LAYERS do drawLayer(layer) end end ---- Funzioni che chiamano funzioni che chiamano funzioni, tre livelli di profondità, e ognuna è tre o quattro righe che riesci a tenere in testa tutte insieme. Ora `onOverlayUpdate` può dire che cosa *è* un fotogramma: [source,lua] ---- function onOverlayUpdate() drift = drift + 1 overlayClear() drawStars() return OVERLAY_UPDATED end ---- Ecco la promessa fatta alla fine della quarta lezione, mantenuta. [#_put_the_ship_back] === Rimetti l'astronave Ora il quinto lavoro, quello che avrebbe fatto un disastro della vecchia `onOverlayUpdate`. Riporta il riquadro che guidavi nella terza lezione, stavolta come un'astronave ferma vicino al fondo dello schermo che si muove solo a destra e a sinistra. Le servono alcuni valori suoi, su in cima insieme agli altri, e le due callback dei comandi della terza lezione senza i rami per su e giù: [source,lua] ---- PLAYER_WIDTH = 20 PLAYER_HEIGHT = 6 PLAYER_SPEED = 3 PLAYER_MARGIN = 6 playerX = width // 2 playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN movingLeft = false movingRight = false ---- [source,lua] ---- function onInputPressed(what) if what == SWITCH_LEFT then movingLeft = true elseif what == SWITCH_RIGHT then movingRight = true end end function onInputReleased(what) if what == SWITCH_LEFT then movingLeft = false elseif what == SWITCH_RIGHT then movingRight = false end end ---- Disegnarla sono due righe, quindi è una funzione: [source,lua] ---- function drawPlayer() colorForeground(80, 255, 120) overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT) end ---- Muoverla è il punto in cui la lezione dà i suoi frutti. Nella terza lezione, tenere il riquadro sullo schermo richiedeva quattro righe di `if` ed `elseif` per ogni direzione. Quell'idea -- tenere un numero tra un minimo e un massimo -- ha un nome, e il nome è *clamp*: [source,lua] ---- function clamp(value, low, high) if value < low then return low end if value > high then return high end return value end ---- Tre return, e al massimo uno di loro avviene, perché `return` non si limita a restituire una risposta: ferma la funzione di colpo. Niente di quello che viene dopo viene eseguito. Se il valore è sotto il minimo, restituisci il minimo e fermati. Se è sopra il massimo, restituisci il massimo e fermati. Altrimenti restituisci quello che è entrato. [source,lua] ---- function movePlayer() if movingLeft then playerX = playerX - PLAYER_SPEED end if movingRight then playerX = playerX + PLAYER_SPEED end playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1) end ---- Una funzione che finisce senza un `return` restituisce niente, cioè `nil`. Va bene per `drawPlayer` e `movePlayer`, che vengono chiamate per fare qualcosa e non per rispondere a qualcosa. È un bug in una funzione come `clamp`: togli quell'ultimo `return value` e ogni posizione in mezzo all'intervallo torna indietro come `nil`. [#_where_names_live] === Dove vivono i nomi Ogni nome che hai creato dalla seconda lezione in poi è stato una *variabile globale*. Una globale appartiene a tutto il programma: `drift`, `playerX` e `LAYERS` possono essere letti e scritti da qualsiasi funzione del file, in qualsiasi momento, e restano lì finché il gioco gira. Sembra comodo, e con sei nomi lo è. I guai cominciano a sessanta. Ogni globale è un nome che tutto il programma ha accettato di riservare, quindi quando un'altra funzione vuole un posto dove tenere una posizione orizzontale e lo chiama anche lei `starX`, i due sono la stessa scatola, e ognuno scrive sopra il numero dell'altro. Quando una globale contiene qualcosa di sbagliato, qualsiasi cosa nel file potrebbe avercelo messo, e scoprire che cosa vuol dire leggerlo tutto. La cura è una *locale*. Metti la parola `local` davanti a un nome la prima volta che lo usi, e il nome esiste solo dentro la `function`, il ciclo o il blocco `if` in cui è stato scritto. [source,lua] ---- function starAt(star, layer) local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height return x, y end ---- Qui `x` e `y` non sono affari di nessun altro. Un'altra funzione può avere la sua `x` e le due non si incontrano mai. Quando la `x` di `starAt` è sbagliata, le tre righe qui sopra sono le uniche tre che possono averlo fatto. Senza la parola `local`, quelle due righe creerebbero in silenzio altre due globali, che è esattamente ciò che `starX` e `starY` sono sempre stati. Ora la parte che sorprende tutti. Scrivi questa funzione, e metti in `onOverlayUpdate` una riga che ti mostri che cosa restituisce: [source,lua] ---- function countUp() local counted = 0 counted = counted + 1 return counted end ---- [source,lua] ---- overlayPrint(0, 0, "Count: " .. countUp()) ---- `Count: 1`, e resta `Count: 1` finché il gioco gira, anche se quella funzione viene chiamata sessanta volte al secondo e aggiunge uno ogni volta. Una locale viene creata nuova quando la funzione comincia e sparisce quando la funzione finisce, quindi `counted` nasce come `0`, diventa `1` e muore, sessanta volte al secondo, per sempre. Una locale non ricorda. Quando l'hai visto, togli di nuovo le due cose. Non è un difetto, è proprio lo scopo: una locale non può essere avvelenata dalla chiamata precedente, perché della chiamata precedente non resta niente. Quando invece devi ricordare qualcosa tra una chiamata e l'altra -- `drift` e `playerX` devono ricordare -- il nome deve vivere fuori da tutte le funzioni. Mettilo in cima al file con `local` davanti: [source,lua] ---- local drift = 0 local playerX = width // 2 ---- Un `local` scritto in cima al file, fuori da tutte le funzioni, vive quanto lo script e può essere visto da ogni funzione che sta sotto. Quell'ultima parola conta: *sotto*. Una locale esiste dalla sua riga in giù, quindi una funzione scritta sopra la riga che la crea non può vederla. Lo stesso vale per le funzioni stesse una volta che le segni come `local`, ed è per questo che lo script finito è nell'ordine in cui è: prima i valori, poi le tue funzioni, poi, per ultime, le callback. Adesso passa tutto il file e metti `local` davanti a ogni nome che hai creato -- le costanti in maiuscolo, `width` e `height`, `drift`, i quattro valori del giocatore e ognuna delle tue funzioni. Tre nomi non lo ricevono: `onInputPressed`, `onInputReleased` e `onOverlayUpdate` devono restare globali, perché Singe le trova per nome e un nome locale è invisibile da fuori del file. Sii onesto su quello che le locali non risolvono. Se sbagli a scrivere un nome a cui stai assegnando un valore -- `movinLeft = true` invece di `movingLeft = true` -- Lua non si lamenta. Crea una globale nuova di zecca con il tuo errore di battitura come nome, la imposta e lascia stare quella vera. Non si muove niente, non c'è nessun errore, e tu lo fissi per dieci minuti. Da quello le locali non ti salvano. Leggere il nome due volte sì. [#_the_whole_thing_4] === Tutto insieme Ecco il programma finito, con dentro ogni pezzo della lezione. [source,lua] ---- local LAYERS = 3 local STARS_PER_LAYER = 40 local ACROSS_STEP = 37 local DOWN_STEP = 61 local LAYER_SHIFT = 13 local LAYER_SPEED = 0.5 local LAYER_BRIGHT = 55 local PLAYER_WIDTH = 20 local PLAYER_HEIGHT = 6 local PLAYER_SPEED = 3 local PLAYER_MARGIN = 6 local width = overlayGetWidth() local height = overlayGetHeight() local drift = 0 local playerX = width // 2 local playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN local movingLeft = false local movingRight = false local function clamp(value, low, high) if value < low then return low end if value > high then return high end return value end local function shadeFor(layer) return math.random(60, 90) + layer * LAYER_BRIGHT end local function starAt(star, layer) local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height return x, y end local function drawStar(x, y, shade) colorForeground(shade, shade, shade) overlayPlot(x, y) end local function drawLayer(layer) for star = 1, STARS_PER_LAYER do local x, y = starAt(star, layer) drawStar(x, y, shadeFor(layer)) end end local function drawStars() for layer = 1, LAYERS do drawLayer(layer) end end local function drawPlayer() colorForeground(80, 255, 120) overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT) end local function movePlayer() if movingLeft then playerX = playerX - PLAYER_SPEED end if movingRight then playerX = playerX + PLAYER_SPEED end playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1) end function onInputPressed(what) if what == SWITCH_LEFT then movingLeft = true elseif what == SWITCH_RIGHT then movingRight = true end end function onInputReleased(what) if what == SWITCH_LEFT then movingLeft = false elseif what == SWITCH_RIGHT then movingRight = false end end function onOverlayUpdate() drift = drift + 1 overlayClear() movePlayer() drawStars() drawPlayer() return OVERLAY_UPDATED end ---- Prima di andare avanti vale la pena indicare due cose. `local function drawStar(x, y, shade)` segna come locale la funzione stessa. Le tue funzioni sono nomi come tutti gli altri, e appartengono solo a questo file. Le tre callback restano globali così Singe può trovarle. E ora `onOverlayUpdate` è sei righe che dicono che cos'è un fotogramma: conta la discesa, pulisci lo schermo, muovi il giocatore, disegna il cielo, disegna il giocatore, fatto. Fa cinque lavori nominando cinque cose che fanno un lavoro ciascuna, che è la forma di ogni ciclo di gioco che scriverai. [#_a_function_is_a_name_for_an_idea] === Una funzione è un nome per un'idea `clamp` viene chiamata una volta sola. Tirarla fuori non ha risparmiato nessuna battitura -- quattro righe sono uscite, una è entrata, e da un'altra parte è comparsa una funzione di nove righe. Valeva comunque la pena farlo. Una funzione non è solo un modo per non ripeterti. È un modo per dare un nome a un'idea, così il codice che usa l'idea può dire il nome e andare avanti. «Tieni il giocatore tra il bordo sinistro e il bordo destro» è un pensiero che hai avuto una volta. `clamp(playerX, 0, width - PLAYER_WIDTH - 1)` è quel pensiero, messo per iscritto. Quattro istruzioni `if` sono lo stesso pensiero smontato in pezzi che devi rimettere insieme in testa ogni volta che li leggi. Quando stai decidendo se una cosa merita di essere una funzione, non chiederti quante volte la chiamerai. Chiediti se ha un nome. [#_try_it_5] === Prova tu . *Rinomina un parametro.* In `drawStar`, cambia `x` e `y` in `across` e `down`, ovunque dentro la funzione. Non toccare niente fuori. Eseguilo. Scopri perché non si è rotto niente. . *Disegnali al contrario.* Scambia `drawStars()` e `drawPlayer()` in `onOverlayUpdate` e guarda l'astronave. Poi rimettili a posto. Ognuna di quelle cinque righe è un lavoro, e il loro ordine sullo schermo è il loro ordine nella funzione. . *Usa `clamp` su una sfumatura.* Cambia `shadeFor` in `return clamp(math.random(60, 90) + layer * 90, 0, 200)` e guarda i tre strati. Poi togli il `clamp` e guarda di nuovo: `colorForeground` ti teneva in silenzio i numeri sotto il limite da sempre, e ora puoi vedere dove. . *Chiama `drawStar` con due argomenti.* Scrivi `drawStar(100, 100)` da qualche parte in `drawLayer` ed eseguilo. L'errore nomina esattamente la cosa che è mancata. Leggilo prima di rimettere il terzo argomento. . *Sposta una locale.* Prendi la riga `local drift = 0` e spostala in fondo al file, sotto tutto il resto. Eseguilo, leggi l'errore e pensa alla parola «sotto» nella sezione precedente. [#_break_it_on_purpose_5] === Rompilo apposta L'ordine di un file con dentro delle locali conta, e l'errore che ottieni quando rompi quell'ordine non è quello che ti aspetti. Prendi tutta la funzione `drawStar` e spostala in *fondo* allo script, sotto `onOverlayUpdate`. C'è ancora tutto, scritto allo stesso modo. Eseguilo: ---- Error executing function 'onOverlayUpdate': functions/functions.singe:50: attempt to call a nil value (global 'drawStar') stack traceback: functions/functions.singe:50: in upvalue 'drawLayer' functions/functions.singe:57: in upvalue 'drawStars' functions/functions.singe:101: in function 'onOverlayUpdate' ---- Ha un aspetto diverso dall'errore della prima lezione perché è un tipo di errore diverso. L'errore della prima lezione è stato colto prima che lo script partisse. Questo è stato colto mentre era in esecuzione, ed è per questo che Singe nomina la funzione in cui si trovava, e per questo sotto c'è un *traceback*: l'elenco di chi ha chiamato chi, dal più recente. La riga 50 è dentro `drawLayer`, che è stata chiamata dalla riga 57 dentro `drawStars`, che è stata chiamata dalla riga 101 dentro `onOverlayUpdate`. I tuoi numeri di riga saranno diversi se il tuo file non corrisponde esattamente a quello qui sopra, e il motore potrebbe aggiungere sotto queste una o due righe sue. La parola `upvalue` è il nome che Lua dà a una locale che una funzione ha preso in prestito dal file che la circonda; per ora passaci sopra. La lamentela in sé vale la pena di impararla a memoria. «Attempt to call a nil value» vuol dire che hai messo delle parentesi dopo un nome che non conteneva niente. La parola `global` davanti a `drawStar` è la parte che svela tutto: hai segnato `drawStar` come locale, quindi quando Lua ha letto `drawLayer` e non ha trovato da nessuna parte sopra una locale con quel nome, l'unica cosa che restava da provare era una globale -- e non c'è nemmeno una globale chiamata `drawStar`. Rimetti la funzione sopra il codice che la chiama e l'errore se ne va con lei. [#_what_you_learned_5] === Cosa hai imparato * Puoi scrivere funzioni tutte tue, e niente le esegue tranne le tue chiamate. * I nomi tra le parentesi di una funzione sono parametri, e sono le copie personali della funzione di quello che ha passato chi la chiama. * `return` restituisce un valore e ferma la funzione lì dov'è, e una funzione che finisce senza di esso restituisce `nil`. * Una chiamata può essere un argomento di un'altra chiamata, e quella interna avviene per prima. * Lua può restituire più di un valore, e li raccogli con più nomi separati da virgole. * `local` crea un nome che esiste solo dove è stato scritto. * Una locale dentro una funzione viene creata nuova a ogni chiamata e non ricorda niente di quella precedente. * Una locale si vede solo sotto la riga che la crea, quindi i valori e le tue funzioni vanno sopra le callback che li usano. * Le callback restano globali, perché Singe le trova per nome. * Vale la pena scrivere una funzione quando la cosa che fa ha un nome, anche se la chiami una volta sola. [#_next_time_5] === La prossima volta Le tue centoventi stelle sono ancora centoventi copie di tre idee. Niente nel programma è *una stella*: c'è un numero, e un po' di aritmetica che trasforma il numero in una posizione. Non puoi dire «questa è più vicina, quindi cade più in fretta e brilla di più», perché non c'è nessun «questa» di cui dirlo, e non puoi lasciare che una stella cada fuori dal fondo e venga sostituita da una nuova, perché lì non c'è niente da togliere. La sesta lezione ti dà la cosa che contiene una stella, e poi un modo per contenerne centoventi e aggiungerne una centoventunesima mentre il gioco gira. È la lezione più utile di questa parte del libro.