[#_lesson_5_your_own_functions] == Leçon 5 : Vos propres fonctions image::learn/05-functions.png[La leçon terminée, 480] La leçon quatre s'est terminée sur un avertissement à propos de votre propre programme : `onOverlayUpdate` calcule où vont cent vingt étoiles, décide de la luminosité de chacune, les dessine et compte la dérive, et c'est une seule fonction qui fait quatre travaux. On est sur le point de lui en demander un cinquième, parce qu'un champ d'étoiles sans rien devant n'est pas un jeu. Dans cette leçon, vous le démontez. Vous allez écrire vos propres fonctions -- des fonctions dont Singe n'a jamais entendu parler et qu'il n'appellera jamais -- et leur confier le travail. À la fin, tout le champ d'étoiles tient en une ligne qui dit `drawStars()`, un vaisseau est revenu en bas de l'écran, et le programme est plus facile à lire que lorsqu'il en faisait moins. [#_start_a_new_folder_4] === Créez un nouveau dossier Un dossier appelé `functions`, dedans un fichier appelé `functions.singe`, et `Singe -R functions` qui tourne dans un terminal. Copiez-y le programme terminé de la leçon quatre. Le voici, inchangé, et ça vaut la peine de le lancer une fois avant de toucher à quoi que ce soit : [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 ---- Trois couches d'étoiles qui descendent dans la fenêtre à trois vitesses. Rien dans la première moitié de cette leçon ne change ce que vous voyez. Tout ce qu'elle contient change la façon dont le programme se lit. [#_one_job_one_name] === Un travail, un nom Commencez par les deux lignes qui mettent une étoile à l'écran. Au-dessus de `onOverlayUpdate`, ajoutez ceci : [source,lua] ---- function drawStar(x, y, shade) colorForeground(shade, shade, shade) overlayPlot(x, y) end ---- Puis, dans la boucle intérieure, supprimez ces deux lignes et appelez-la à la place : [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 ---- Enregistrez. Rien ne change à l'écran. Vous avez déjà écrit des fonctions -- `onOverlayUpdate` en est une, et `onInputPressed` et `onInputReleased` de la leçon trois aussi -- mais ce sont des callbacks. Vous les écrivez et Singe les appelle. `drawStar` est différente. Singe n'en a jamais entendu parler. Elle s'exécute parce que *vous* avez écrit son nom et des parenthèses, de la même façon que vous appelez `overlayPlot`, et rien au monde ne l'exécutera si vous ne le faites pas. Tout le truc est là. Désormais, quand vous vous surprenez à penser « et maintenant, dessiner une étoile », vous pouvez écrire « dessiner une étoile », et c'est bien ce que ça voudra dire. [#_a_parameter_is_a_name_not_a_thing] === Un paramètre est un nom, pas une chose Regardez de nouveau la première ligne de `drawStar`. [source,lua] ---- function drawStar(x, y, shade) ---- Les trois noms entre les parenthèses sont des *paramètres*. Un paramètre est un nom que la fonction utilise pour ce que l'appelant lui a passé, quoi que ce soit. Quand vous écrivez `drawStar(starX, starY, shade)`, Lua met dans `x` ce qu'il y a dans `starX`, dans `y` ce qu'il y a dans `starY`, dans `shade` ce qu'il y a dans `shade`, puis exécute la fonction. Les noms n'ont rien à voir entre eux. Le nombre de l'appelant s'appelle `starX` là-bas et `x` ici, et ça marcherait exactement pareil si vous l'appeliez `across` ou `banana`. À l'intérieur de la fonction, seuls les noms entre les parenthèses existent, et ils contiennent des copies de ce qui est entré. C'est ici que les parenthèses vides de la leçon un prennent enfin leur sens. [source,lua] ---- function onOverlayUpdate() ---- Rien entre les parenthèses veut dire aucun paramètre : le moteur n'a rien à dire à cette fonction, donc il n'y a rien à nommer. Comparez avec le callback que vous avez écrit à la leçon trois. [source,lua] ---- function onInputPressed(what) ---- Un paramètre, appelé `what`. Le moteur a quelque chose à dire à celui-ci -- quelle commande a été enfoncée -- donc il y a un nom pour l'attraper. Vous vous servez d'un paramètre depuis la leçon trois. Un paramètre est vraiment la copie personnelle de la fonction, et ça vaut la peine de vous le prouver une fois. Tapez ceci, appelez-la depuis `onOverlayUpdate`, et retirez-la ensuite : [source,lua] ---- function half(number) number = number // 2 return number end ---- Appelez-la sous la forme `half(drift)`. La fonction divise son `number` par deux et renvoie la réponse, et `drift`, là-bas dans `onOverlayUpdate`, continue de compter comme si de rien n'était. Une fonction ne peut pas aller changer les variables de l'appelant à travers un paramètre. Elle reçoit une copie, et ce qu'elle fait de la copie ne regarde qu'elle. [#_handing_an_answer_back] === Renvoyer une réponse `half` a fait quelque chose que les fonctions que vous avez écrites jusqu'ici ne faisaient pas : elle a donné une réponse. C'est la dernière ligne. [source,lua] ---- return number ---- `return` renvoie une valeur à celui qui a appelé la fonction, et l'appel se transforme en cette valeur. Écrivez `smaller = half(drift)` et Lua exécute `half`, prend ce que `half` a renvoyé et le met dans `smaller`. Vous avez rencontré `return` à la leçon un, où `onOverlayUpdate` renvoie `OVERLAY_UPDATED` pour répondre à la question de Singe : est-ce que quelque chose a changé ? C'est le même mot qui fait le même travail, sauf que maintenant, c'est vous qui posez la question. La ligne de la luminosité ne demande qu'à devenir une fonction qui donne une réponse. Ajoutez celle-ci : [source,lua] ---- function shadeFor(layer) return math.random(60, 90) + layer * LAYER_BRIGHT end ---- Tout le corps est un `return`, ce qui est normal pour une petite fonction. Maintenant, la boucle intérieure peut passer la réponse directement à `drawStar`, sans nom entre les deux : [source,lua] ---- drawStar(starX, starY, shadeFor(layer)) ---- Un appel dans un appel. Lua calcule d'abord celui de l'intérieur, reçoit un nombre de `shadeFor`, et passe ce nombre à `drawStar` comme troisième argument. Vous avez déjà écrit quelque chose de ce genre sans le remarquer : le `math.floor(drift * layer * LAYER_SPEED)` de la leçon quatre est un appel dont la réponse est utilisée sur place, au milieu d'un calcul plus grand. [#_two_answers_at_once] === Deux réponses à la fois Les deux lignes de position sont plus difficiles, parce que ce sont deux nombres calculés ensemble et qu'il faudrait en choisir un à renvoyer. Dans la plupart des langages, une fonction peut renvoyer une valeur, et en obtenir deux est une corvée. Lua vous laisse en renvoyer autant que vous voulez. [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 ---- Vous attrapez les réponses en mettant deux noms à gauche, séparés par une virgule : [source,lua] ---- local x, y = starAt(star, layer) ---- La première valeur dans le premier nom, la deuxième dans le deuxième. Si vous demandez moins de noms que la fonction ne renvoie de valeurs, les valeurs en trop sont jetées : `local x = starAt(star, layer)` vous donne le nombre horizontal et perd le vertical, sans se plaindre. Si vous demandez plus de noms qu'il n'y a de valeurs, les noms en trop valent `nil`. Ignorez le mot `local` dans ces deux blocs encore une minute. Il aura bientôt sa propre partie, et c'est la plus importante. [#_the_star_field_in_one_line] === Le champ d'étoiles en une ligne La boucle intérieure tient maintenant en trois lignes courtes, qui parlent toutes d'une couche d'étoiles. C'est un travail qui a un nom. [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 ---- Et la boucle extérieure est elle aussi un travail qui a un nom : [source,lua] ---- function drawStars() for layer = 1, LAYERS do drawLayer(layer) end end ---- Des fonctions qui appellent des fonctions qui appellent des fonctions, sur trois niveaux, et chacune fait trois ou quatre lignes que vous pouvez garder en tête d'un seul coup. `onOverlayUpdate` peut maintenant dire ce qu'**est** une image : [source,lua] ---- function onOverlayUpdate() drift = drift + 1 overlayClear() drawStars() return OVERLAY_UPDATED end ---- Voilà la promesse faite à la fin de la leçon quatre, tenue. [#_put_the_ship_back] === Remettez le vaisseau Maintenant, le cinquième travail, celui qui aurait mis le désordre dans l'ancien `onOverlayUpdate`. Ramenez la boîte que vous pilotiez à la leçon trois, cette fois sous la forme d'un vaisseau posé près du bas de l'écran, qui ne bouge que vers la gauche et la droite. Il lui faut quelques valeurs à lui, en haut avec les autres, et les deux callbacks d'entrée de la leçon trois, sans les branches pour le haut et le bas : [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 ---- Le dessiner prend deux lignes, donc c'est une fonction : [source,lua] ---- function drawPlayer() colorForeground(80, 255, 120) overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT) end ---- C'est en le déplaçant que la leçon porte ses fruits. À la leçon trois, garder la boîte à l'écran prenait quatre lignes de `if` et de `elseif` pour chaque direction. Cette idée -- tenir un nombre entre un plancher et un plafond -- a un nom, et ce nom, c'est *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 ---- Trois sorties, et une seule au plus se produit, parce que `return` ne fait pas que renvoyer une réponse : il arrête net la fonction. Rien de ce qui suit ne s'exécute. Si la valeur est sous le plancher, renvoyez le plancher et arrêtez. Si elle dépasse le plafond, renvoyez le plafond et arrêtez. Sinon, renvoyez ce qui est entré. [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 ---- Une fonction qui se termine sans `return` ne renvoie rien, c'est-à-dire `nil`. C'est ce qu'il faut pour `drawPlayer` et `movePlayer`, qu'on appelle pour faire quelque chose plutôt que pour répondre à quelque chose. C'est un bug dans une fonction comme `clamp` : oubliez ce dernier `return value` et chaque position au milieu de la plage revient sous la forme `nil`. [#_where_names_live] === Où vivent les noms Chaque nom que vous avez créé depuis la leçon deux était un nom *global*. Un nom global appartient à tout le programme : `drift`, `playerX` et `LAYERS` peuvent être lus et écrits par n'importe quelle fonction du fichier, à tout moment, et ils restent là tant que le jeu tourne. Ça a l'air pratique, et pour six noms, ça l'est. Les ennuis commencent à soixante. Chaque nom global est un nom que tout le programme s'est engagé à réserver, donc quand une autre fonction veut un endroit où garder une position horizontale et l'appelle `starX` elle aussi, les deux sont la même boîte, et chacune écrit par-dessus le nombre de l'autre. Quand un nom global contient quelque chose de faux, n'importe quoi dans le fichier a pu l'y mettre, et trouver quoi veut dire tout relire. Un nom *local* est le remède. Mettez le mot `local` devant un nom la première fois que vous l'utilisez, et le nom n'existe qu'à l'intérieur de la `function`, de la boucle ou du `if` où il a été écrit. [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 ---- Ici, `x` et `y` ne regardent personne d'autre. Une autre fonction peut avoir son propre `x`, et les deux ne se rencontrent jamais. Quand le `x` de `starAt` est faux, les trois lignes ci-dessus sont les trois seules qui ont pu le provoquer. Sans le mot `local`, ces deux lignes créeraient discrètement deux noms globaux de plus, et c'est exactement ce que `starX` et `starY` étaient depuis le début. Maintenant, la partie qui surprend tout le monde. Tapez cette fonction, et mettez dans `onOverlayUpdate` une ligne qui vous montre ce qu'elle renvoie : [source,lua] ---- function countUp() local counted = 0 counted = counted + 1 return counted end ---- [source,lua] ---- overlayPrint(0, 0, "Count: " .. countUp()) ---- `Count: 1`, et ça reste `Count: 1` tant que le jeu tourne, alors que la fonction est appelée soixante fois par seconde et ajoute un à chaque fois. Un nom local est créé tout neuf quand la fonction commence et disparaît quand elle se termine, donc `counted` naît à `0`, devient `1` et meurt, soixante fois par seconde, pour toujours. Un nom local ne se souvient pas. Retirez les deux une fois que vous avez regardé. Ce n'est pas un défaut, c'est le but : un nom local ne peut pas être empoisonné par l'appel précédent, parce qu'il ne reste rien de l'appel précédent. Quand vous avez vraiment besoin de vous souvenir de quelque chose d'un appel à l'autre -- `drift` et `playerX` doivent se souvenir -- le nom doit vivre en dehors de toute fonction. Mettez-le en haut du fichier, avec `local` devant : [source,lua] ---- local drift = 0 local playerX = width // 2 ---- Un `local` écrit en haut du fichier, en dehors de toutes les fonctions, vit aussi longtemps que le script et peut être vu par toutes les fonctions en dessous. Ce dernier mot compte : *en dessous*. Un nom local existe à partir de sa propre ligne et vers le bas, donc une fonction écrite au-dessus de la ligne qui le crée ne peut pas le voir. C'est vrai aussi des fonctions elles-mêmes, une fois que vous les marquez `local`, et c'est pourquoi le script terminé est dans cet ordre : les valeurs d'abord, puis vos propres fonctions, puis les callbacks, en dernier. Parcourez maintenant tout le fichier et mettez `local` devant chaque nom que vous avez créé -- les constantes en majuscules, `width` et `height`, `drift`, les quatre valeurs du joueur et chacune de vos propres fonctions. Trois noms n'y ont pas droit : `onInputPressed`, `onInputReleased` et `onOverlayUpdate` doivent rester globaux, parce que Singe les trouve par leur nom et qu'un nom local est invisible depuis l'extérieur du fichier. Soyons honnêtes sur ce que les noms locaux ne règlent pas. Si vous faites une faute de frappe dans un nom auquel vous affectez une valeur -- `movinLeft = true` au lieu de `movingLeft = true` -- Lua ne se plaint pas. Il crée un tout nouveau nom global avec votre faute de frappe pour nom, le remplit, et laisse le vrai tranquille. Rien ne bouge, aucune erreur, et vous fixez l'écran pendant dix minutes. Les noms locaux ne vous sauveront pas de celle-là. Relire le nom deux fois, si. [#_the_whole_thing_4] === Le tout Voici le programme terminé, avec chaque morceau de la leçon dedans. [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 ---- Deux choses méritent d'être montrées du doigt avant de passer à la suite. `local function drawStar(x, y, shade)` marque la fonction elle-même comme locale. Vos propres fonctions sont des noms comme les autres, et elles n'appartiennent qu'à ce fichier. Les trois callbacks restent globaux pour que Singe puisse les trouver. Et `onOverlayUpdate` fait maintenant six lignes qui disent ce qu'est une image : compter la dérive, effacer l'écran, déplacer le joueur, dessiner le ciel, dessiner le joueur, terminé. Il fait cinq travaux en nommant cinq choses qui font chacune un travail, et c'est la forme de toutes les boucles de jeu que vous écrirez jamais. [#_a_function_is_a_name_for_an_idea] === Une fonction est le nom d'une idée `clamp` n'est appelée qu'une fois. La sortir n'a économisé aucune frappe -- quatre lignes en sont sorties, une seule y est entrée, et une fonction de neuf lignes est apparue ailleurs. Ça valait quand même la peine. Une fonction n'est pas seulement un moyen d'éviter de vous répéter. C'est un moyen de donner un nom à une idée, pour que le code qui se sert de l'idée puisse dire le nom et passer à autre chose. « Garder le joueur entre le bord gauche et le bord droit » est une pensée que vous avez eue une fois. `clamp(playerX, 0, width - PLAYER_WIDTH - 1)`, c'est cette pensée, écrite. Quatre instructions `if`, c'est la même pensée démontée en morceaux que vous devez remonter dans votre tête chaque fois que vous les lisez. Quand vous vous demandez si quelque chose mérite d'être une fonction, ne vous demandez pas combien de fois vous l'appellerez. Demandez-vous si ça a un nom. [#_try_it_5] === Essayez . *Renommez un paramètre.* Dans `drawStar`, changez `x` et `y` en `across` et `down`, partout dans la fonction. Ne touchez à rien en dehors. Lancez. Trouvez pourquoi rien n'a cassé. . *Dessinez-les dans l'autre ordre.* Échangez `drawStars()` et `drawPlayer()` dans `onOverlayUpdate` et regardez le vaisseau. Puis remettez-les. Chacune de ces cinq lignes est un travail, et leur ordre à l'écran est leur ordre dans la fonction. . *Utilisez `clamp` sur une nuance.* Changez `shadeFor` en `return clamp(math.random(60, 90) + layer * 90, 0, 200)` et regardez les trois couches. Puis retirez le `clamp` et regardez encore : `colorForeground` retenait discrètement les nombres pour vous depuis le début, et maintenant vous voyez où. . *Appelez `drawStar` avec deux arguments.* Écrivez `drawStar(100, 100)` quelque part dans `drawLayer` et lancez. L'erreur nomme exactement ce qui manquait. Lisez-la avant de remettre le troisième argument. . *Déplacez un nom local.* Prenez la ligne `local drift = 0` et déplacez-la à la fin du fichier, en dessous de tout. Lancez, lisez l'erreur, et pensez aux mots « en dessous » dans la partie ci-dessus. [#_break_it_on_purpose_5] === Cassez-le exprès L'ordre d'un fichier qui contient des noms locaux compte, et l'erreur que vous obtenez quand vous cassez cet ordre n'est pas celle que vous attendez. Prenez toute la fonction `drawStar` et déplacez-la tout *en bas* du script, sous `onOverlayUpdate`. Tout est encore là, écrit pareil. Lancez : ---- 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' ---- Elle n'a pas la même allure que l'erreur de la leçon un, parce que c'est une autre sorte d'erreur. L'erreur de la leçon un a été attrapée avant que le script s'exécute. Celle-ci a été attrapée pendant qu'il s'exécutait, c'est pourquoi Singe nomme la fonction dans laquelle il était, et pourquoi il y a un *traceback* dessous : la liste de qui a appelé qui, le plus récent en premier. La ligne 50 est dans `drawLayer`, qui a été appelée depuis la ligne 57, dans `drawStars`, qui a été appelée depuis la ligne 101, dans `onOverlayUpdate`. Vos numéros de ligne seront différents si votre fichier ne correspond pas exactement à celui ci-dessus, et le moteur peut ajouter une ligne ou deux de son cru sous celles-ci. Le mot `upvalue` est le nom que Lua donne à un nom local qu'une fonction a emprunté au fichier qui l'entoure ; passez dessus pour l'instant. La plainte elle-même vaut la peine d'être apprise par cœur. « Attempt to call a nil value » veut dire que vous avez mis des parenthèses après un nom qui ne contenait rien. Le mot `global` devant `drawStar`, c'est la partie qui vend la mèche : vous avez marqué `drawStar` comme locale, donc quand Lua a lu `drawLayer` et n'a trouvé aucun nom local qui s'appelle ainsi nulle part au-dessus, il ne restait plus qu'à essayer un nom global -- et il n'y a pas non plus de nom global appelé `drawStar`. Remettez la fonction au-dessus du code qui l'appelle, et l'erreur s'en va avec. [#_what_you_learned_5] === Ce que vous avez appris * Vous pouvez écrire vos propres fonctions, et rien ne les exécute à part vos propres appels. * Les noms entre les parenthèses d'une fonction sont des paramètres, et ce sont les copies, propres à la fonction, de ce que l'appelant a passé. * `return` renvoie une valeur et arrête la fonction sur-le-champ, et une fonction qui se termine sans en avoir un renvoie `nil`. * Un appel peut être l'argument d'un autre appel, et celui de l'intérieur se fait en premier. * Lua peut renvoyer plus d'une valeur, et vous les attrapez avec plusieurs noms séparés par des virgules. * `local` crée un nom qui n'existe que là où il a été écrit. * Un nom local dans une fonction est créé tout neuf à chaque appel et ne se souvient de rien de l'appel précédent. * Un nom local ne peut être vu que sous la ligne qui le crée, donc les valeurs et vos propres fonctions vont au-dessus des callbacks qui s'en servent. * Les callbacks restent globaux, parce que Singe les trouve par leur nom. * Une fonction vaut la peine d'être écrite quand ce qu'elle fait a un nom, même si vous ne l'appelez qu'une fois. [#_next_time_5] === La prochaine fois Vos cent vingt étoiles sont toujours cent vingt copies de trois idées. Rien dans le programme n'est *une étoile* : il y a un nombre, et un peu d'arithmétique qui transforme ce nombre en position. Vous ne pouvez pas dire « celle-ci est plus proche, donc elle tombe plus vite et brille plus fort », parce qu'il n'y a pas de « celle-ci » dont le dire, et vous ne pouvez pas laisser une étoile tomber par le bas et être remplacée par une nouvelle, parce qu'il n'y a rien là à retirer. La leçon six vous donne la chose qui contient une étoile, puis un moyen d'en contenir cent vingt et d'en ajouter une cent vingt et unième pendant que le jeu tourne. C'est la leçon la plus utile de cette partie du livre.