Aller au contenu

Initiation au Lua avec Scribunto/Fonctions basiques

Leçons de niveau 10
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
Fonctions basiques
Icône de la faculté
Chapitre no 5
Leçon : Initiation au Lua avec Scribunto
Chap. préc. :Structures de contrôle
Chap. suiv. :Chaînes de caractères

Exercices :

Sur les fonctions basiques
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Initiation au Lua avec Scribunto : Fonctions basiques
Initiation au Lua avec Scribunto/Fonctions basiques
 », n'a pu être restituée correctement ci-dessus.

Au paragraphe précédent, nous avons étudié les fonctions. Si nous créons une fonction, c’est pour effectuer généralement un travail répétitif. Plutôt que de répéter une même séquence d'instructions dans plusieurs parties d'un programme, nous créons une fonction contenant cette séquence et nous utilisons cette fonction en différentes parties du programme. Sur ce principe, le Lua met à la disposition des utilisateurs un certain nombre de fonctions pré-créées. Les concepteurs du Lua ont remarqué que certains besoins se faisaient souvent sentir dans l'écriture d'un programme et ont donc préprogrammé un certain nombre de fonctions que nous allons commencer à étudier dans ce chapitre. Dans ce chapitre, nous verrons certaines des fonctions préprogrammées les plus basiques, qui ne sont pas spécialisées dans un domaine particulier. Dans les chapitres suivants, nous verrons des fonctions plus spécialisées dans un domaine particulier comme les mathématiques ou les chaînes de caractères.

L'ordre dans lequel nous étudierons les différentes fonctions ne sera pas alphabétique. Nous essaierons d'adopter un ordre pédagogique en étudiant les fonctions dans l’ordre de difficulté et aussi pratique en étudiant d’abord les fonctions utiles pour mieux comprendre les suivantes.

Tous les exemples de ce chapitre se trouve dans le Module:Basique


La fonction type permet de connaître le type d'objet que contient une variable en retournant l'une des chaîne de caractères suivantes : "nil", "number", "string", "boolean", "table", et "function". Nous avons étudier chacun de ces types dans les chapitres précédent :

  • "nil" : c’est en quelque sorte ce que contient une variable par défaut si elle n’est pas affectée. C'est aussi ce que retourne certaines fonctions lorsque quelque chose s'est mal passé.
  • "number" : indique que la variable contient un nombre.
  • "string" : indique que la variable contient une chaîne de caractères.
  • "boolean" : indique que la variable contient un booléen : true ou false (vraie ou faux).
  • "table" : indique que la variable contient une table.
  • "function" : indique que la variable contient une fonction.

À titre d'exemple, dans un Module:Basique, nous écrirons une fonction toutype qui retourne tous les types possibles en fabriquant une variable qui contiendra successivement tous les types possibles et qui sera, à chaque fois, testée par la fonction type.

local p = {}

function f(x)
	return x^2
end

function p.toutype()
	local reponse = " "
	reponse = reponse..type(cameleon).." " -- La variable cameleon n'a pas été affectée donc la fonction type devrait retourner "nil"
	cameleon = 7
	reponse = reponse..type(cameleon).." "
	cameleon = "miam miam"
	reponse = reponse..type(cameleon).." "
	cameleon = true
	reponse = reponse..type(cameleon).." "
	cameleon = p -- N'oublions pas que p a été déclaré comme étant une table en début de module
	reponse = reponse..type(cameleon).." "
	cameleon = f -- f est la fonction défini un peu plus haut et qui élève un nombre au carré
	reponse = reponse..type(cameleon)
	return reponse
end

return p


{{#invoke:Basique|toutype}} nous retourne : nil number string boolean table function


Nous connaissons déjà, un peu, cette fonction, car nous avons dû l'introduire dès le premier chapitre, compte tenu de son utilité. Cette fonction tente de convertir une chaîne de caractères en nombre. Par exemple, elle convertira la chaîne de caractères "23" en nombre 23. S'il n’est pas possible de convertir la chaîne de caractère en nombre, la fonction retournera nil.

À titre d'exemple, dans le Module:Basique, nous écrirons une fonction nombre qui indique à l'utilisateur si l'argument rentré est une chaîne convertible en nombre.

local p = {}
function p.nombre(frame)
	local n = tonumber(frame.args[1])
	if n == nil then
		return "Je n'ai pas réussi à convertir votre chaine de caractères en nombre"
	else
		return "Vous avez bien rentré un nombre"
	end
end

return p

{{#invoke:Basique|nombre|27}} nous retourne : Vous avez bien rentré un nombre

27 est bien un nombre !


{{#invoke:Basique|nombre|Maison}} nous retourne : Je n'ai pas réussi à convertir votre chaine de caractères en nombre

Une maison n’est pas un nombre !


{{#invoke:Basique|nombre|vingt-sept}} nous retourne : Je n'ai pas réussi à convertir votre chaine de caractères en nombre

La fonction tonumber ne connait pas le français !


{{#invoke:Basique|nombre|eleven}} nous retourne : Je n'ai pas réussi à convertir votre chaine de caractères en nombre

Elle ne connait pas l'anglais non plus !


{{#invoke:Basique|nombre|3.14}} nous retourne : Vous avez bien rentré un nombre

La fonction tonumber reconnait le point décimal !


{{#invoke:Basique|nombre|3,14}} nous retourne : Je n'ai pas réussi à convertir votre chaine de caractères en nombre

Mais elle ne reconnait pas la virgule !


{{#invoke:Basique|nombre|7.5698e-3}} nous retourne : Vous avez bien rentré un nombre

Elle reconnait la notation scientifique !


La fonction tostring est un peu le contraire de la fonction tonumber, car elle convertit son argument en chaîne de caractères.

Comme exemple, nous allons reprendre l'exemple donné pour la fonction type, mais en remplaçant type par tostring :

local p = {}

function f(x)
	return x^2
end

function p.verschaine()
	local reponse = " "
	reponse = reponse..tostring(cameleon).." " -- La variable cameleon n'a pas été affectée donc la fonction type devrait retourner "nil"
	cameleon = 7
	reponse = reponse..tostring(cameleon).." "
	cameleon = "mian mian"
	reponse = reponse..tostring(cameleon).." "
	cameleon = true
	reponse = reponse..tostring(cameleon).." "
	cameleon = p -- N'oublions pas que p a été déclaré comme étant une table en début de module
	reponse = reponse..tostring(cameleon).." "
	cameleon = f -- f est la fonction défini un peu plus haut et qui élève un nombre au carré
	reponse = reponse..tostring(cameleon)
	return reponse
end

return p


{{#invoke:Basique|verschaine}} nous retourne : nil 7 mian mian true table function


Lorsque l’objet est nil, une table ou une fonction, la fonction tostring nous délivre le même message que la fonction type. Lorsque l’objet est une chaîne de caractères, tostring nous rend la chaîne à l'identique. Lorsque l’objet est un nombre, ce nombre est transformé en chaîne en rajoutant des guillemets (7 devient "7"). Lorsque l’objet est un booléen, tostring nous délivre "true" ou "false" selon le cas.


C'est une fonction qui provoque volontairement une erreur de script, mais avec un message d'erreur que l’on a choisi et un contrôle sur la position de l'erreur indiquée par le Lua.

La syntaxe de cette fonction est error(message, niveau). message est le message d'erreur apparaissant lorsque l’on clique sur l'erreur de script(en rouge).

Pour niveau :

S'il y a 0, on n'aura pas de message indiquant la ligne ou la fonction error se trouve.

S'il y a 1 ou rien, on aura un message indiquant la ligne ou la fonction error concernée se trouve.

S'il y a 2, on aura un message indiquant la ligne ou se fait l'appel à la fonction contenant la fonction error concernée.

S'il y a 3, on aura un message indiquant la ligne ou se fait l'appel à une fonction qui a appelé la fonction contenant la fonction error concernée.

Et ainsi de suite..


Prenons un exemple :

local p = {}

function i(x)
	error("turlututu chapeau pointu",3)
end

function h(x)
	i(x)
end

function g(x)
	h(x)
end

function p.erreur()
	g(2)
end

return p

{{#invoke:Basique|erreur}} nous retourne : Erreur Lua dans Module:Basique à la ligne 103 : turlututu chapeau pointu.

En cliquant sur l'erreur de script si dessus, nous obtenons :

Erreur Lua dans Module:Basique à la ligne n:

turlututu chapeau pointu.

Dans la pile des appels, nous voyons que la ligne n (voir la valeur de n en cliquant sur l'erreur de script ci-dessus) correspond à la ligne où a été déclarée la fonction g. la fonction g appelant la fonction h qui appelle la fonction i ou nous avons mis la fonction error, nous sommes bien en conformité avec le fait que nous avons mis 3 comme niveau de la fonction error.


La fonction pcall permet d'appeler une fonction en gérant une éventuelle erreur lors de son exécution.

Dans l'exemple ci-dessous, la fonction p.crash appelle avec pcall une fonction dans laquelle, on a mis volontairement une instruction qui, normalement devrait provoquer une erreur de script. Nous allons voir que nous n'avons pas le fameux message rouge indiquant l'erreur de script car la fonction pcall gère elle-même l'erreur de script. Le premier argument de la fonction pcall est le nom de la fonction appelée, les arguments suivants sont les arguments de la fonction appelée.

local p = {}

function plouf(x)
	local tab ={6,8}
	return x + tab
end

function p.crash()
	local a,b = pcall(plouf,3)
	if a == false then
		return b
	else
		return "Aucun probléme ! La fonction a retourné"..b
	end
end

return p

{{#invoke:Basique|crash}} nous retourne : Module:Basique:112: attempt to perform arithmetic on local 'tab' (a table value)


Dans le programme précédent, la fonction pcall retourne deux valeurs (si la fonction appelée retournait n valeurs, la fonction p.call retournerait n + 1 valeurs).

Dans notre exemple, la fonction p.call appelle la fonction plouf dans laquelle, nous avons mis volontairement une erreur de script (nous tentons l'addition d'un nombre avec une table). Au lieu d’avoir notre habituel message rouge indiquant l'erreur de script, la fonction pcall va retourner en première valeur (a dans notre exemple), la valeur false et en seconde valeur (b dans notre exemple) un message indiquant la ligne ou s'est produite l'erreur de script et la nature de l'erreur de script (voir l'exécution de notre exemple).

S'il n'y avait pas eut d'erreur de script dans la fonction plouf de notre exemple, la fonction p.call aurait retourné en première valeur, la valeur true et en seconde valeur la valeur retournée normalement par la fonction plouf. Si la fonction plouf retournait plusieurs valeurs, la fonction pcall retournerait aussi toutes ces valeurs à la suite de la valeur true.


Voir un autre exemple d'utilisation de la fonction pcall dans le chapitre : Gestion de l'environnement, paragraphe : Gestion des erreurs (troisième sous-paragraphe).


Nous avons vu que les clés des tables pouvaient aussi bien être des nombres que des chaînes de caractères. Dans d'autres langages, l'accès aux tables se fait uniquement avec des nombres et l’on à vu que si l’on souhaitait faire quelque chose sur une table indexée par des nombres, on pouvait le faire facilement en utilisant une boucle for..do. Là où les choses deviennent plus compliquées, c’est quand l’on souhaite travailler sur une table indexée par des chaînes de caractères. Le programmeur habitué, dans un autre langage, aux tables indexées par des nombres, a de quoi être décontenancé. Comment parcourir une table indexée par des chaînes de caractères. C'est ici, qu'intervient la fonction next. Cette fonction a la capacité de nous donner l'index suivant un index donné dans une table ainsi que l’objet correspondant à cet index (cette fonction retourne deux objets). Par exemple, si nous écrivons next(tab, "bouillotte"), tab étant une table et "bouillotte", une chaîne de caractères servant d'index dans la table tab, alors cette instruction nous donnera l'index suivant dans la table et l’objet correspondant ("marmite" par exemple et ce qu’il y a dans la marmitte). Il suffira alors d'écrire : next(tab, "marmite") pour avoir, à nouveau, l'index suivant avec l’objet correspondant et ainsi de suite. On pourra de cette façon parcourir toute la table !

Nous devons préciser deux petits détails :

Si nous ne savons pas quel est le premier index, nous taperons simplement next(tab, nil) et la fonction next nous retournera le premier index et l’objet correspondant. On peut aussi taper simplement next(tab) pour avoir le premier index et l’objet correspondant.

Si "bouillotte" est le dernier index de la table, alors next(tab, "bouillotte") nous retournera nil

Nous devons bien noter ces deux petits détails, car ils peuvent être causes de bien des soucis dans un programme.


À titre d'exemple, nous commencerons par écrire une fonction listecle qui se contentera de retourner tous les index d'une table que l’on aura rentrée avant la fonction ainsi que les objets correspondants:


local Categorie = { ["Prénom"] = "Christine", ["Mois"] = "Avril", ["Métier"] = "Boulanger", ["Poisson"] = "Truite", ["Métal"] = "Argent", ["Planète"] = "Saturne", ["Instrument"] = "Piano"}

function p.listecle()
	local reponse = " "
	local suivant, objet
	for index = 1, 7 do
		suivant,objet = next(Categorie,suivant)
		reponse = reponse.."<br> "..suivant.." correspond à "..objet
	end
	return reponse
end

{{#invoke:Basique|listecle}} nous retourne :
Prénom correspond à Christine
Instrument correspond à Piano
Planète correspond à Saturne
Métal correspond à Argent
Mois correspond à Avril
Poisson correspond à Truite
Métier correspond à Boulanger

La première remarque que l’on peut faire est que les index ne sont pas sortis dans l’ordre où nous les avons écrit ??? Qu'importe, du moment où on les a tous !!! (C'est même pas l’ordre alphabétique d'ailleurs !?)

L'instruction intéressante dans ce programme est l'instruction :

suivant,objet = next(Categorie,suivant)

qui, à chaque boucle, remplace l'index contenu dans la variable suivant par l'index suivant et donne aussi l’objet correspondant.

Le programme précédent est un peu simpliste car il supposait que nous connaissions déjà le contenu de la table et qu’il y avait exactement 7 index. C'est pour cela que nous avons utilisé une boucle for..do. Si l’on remplace dans la programme précédent le 7 de la boucle for..do par un 8, alors nous aurons une belle erreur de script à cause de l'instruction :

reponse = reponse.."<br> "..suivant.." correspond à "..objet

car suivant contiendra nil à la huitième boucle


Si nous ne connaissons pas la table d'avance et que nous ne savons pas combien elle contient de clés, nous devons la parcourir avec une autre instruction de contrôle capable de détecter le nil apparaissant dans la variable suivant lorsque l’on a fini de parcourir la table.

nous allons donc écrire une fonction yatil, moins lamentable que la précédente, en utilisant une boucle repeat..until qui va parcourir la table jusqu'à ce que nil apparaisse dans suivant. Ce programme a pour but de nous indiquer si un mot rentré en paramètre se trouve dans la table :

local Categorie = { ["Prénom"] = "Christine", ["Mois"] = "Avril", ["Métier"] = "Boulanger", ["Poisson"] = "Truite", ["Métal"] = "Argent", ["Planète"] = "Saturne", ["Instrument"] = "Piano"}

function p.yatil(frame)
	local suivant,objet
	local trouve = false
	repeat
		suivant,objet = next(Categorie,suivant) --Cherche la clé suivante et la met dans suivant
		if objet == frame.args[1] then trouve = true end
	until suivant == nil or trouve -- On tourne dans la boucle jusqu'à ce que toute la table ait été parcourue ou jusqu'à ce qu'on ait trouvé
	if trouve then
		return "Le mot figure dans la table"
	else
		return "Le mot ne figure pas dans la table"
	end
end

Nota : Le programme ci-dessus est volontairement mal écrit. Une amélioration de celui-ci est proposée dans l'exercice 9-2.

{{#invoke:Basique|yatil|Truite}} nous retourne : Le mot figure dans la table

{{#invoke:Basique|yatil|Trottinette}} nous retourne : Le mot ne figure pas dans la table


Astuce

Nous avons vu que next(tab) nous fourni la première clé de la table. Si next(tab) nous retourne nil, cela signifie qu’il n'y a pas de première clé dans la table, autrement dit que la table est vide. next(tab) permet donc aussi de tester si une table est vide.


La fonction ipairs nous fourni une façon plus évolué et surtout plus commode de parcourir une table avec clé numérique. Un exemple simple vaut mieux qu'un long discours et va nous permettre de mieux comprendre. Écrivons donc une fonction description qui se contente de dire bêtement ce que l’on trouve à la clé numéro i (i étant un nombre) :

local souk = {"flute", "pipo", "manche à balaie", "serpière", "jeu de cartes", "coton tige", "tourne vis", "rateau", "stylo", "poupée"}

function p.description()
	local reponse = " "
	for index, objet in ipairs(souk) do
		reponse = reponse.."<br>à la clé numéro "..index.." se trouve l’objet "..objet.."."
	end
	return reponse
end

{{#invoke:Basique|description}} nous retourne :
à la clé numéro 1 se trouve l’objet flute.
à la clé numéro 2 se trouve l’objet pipo.
à la clé numéro 3 se trouve l’objet manche à balaie.
à la clé numéro 4 se trouve l’objet serpière.
à la clé numéro 5 se trouve l’objet jeu de cartes.
à la clé numéro 6 se trouve l’objet coton tige.
à la clé numéro 7 se trouve l’objet tourne vis.
à la clé numéro 8 se trouve l’objet rateau.
à la clé numéro 9 se trouve l’objet stylo.
à la clé numéro 10 se trouve l’objet poupée.


Si la table contient, à la fois, des clés numériques et des clés sous forme de chaîne de caractère, la fonction ipairs parcourra les clés numériques en ignorant les clés sous forme de chaîne de caractères.


La fonction pairs réalise à peu près la même chose que la fonction ipairs mais en considérant, cette fois, les tables dont les clés sont des chaînes de caractères. Nous prendrons donc, là aussi, un exemple pour mieux comprendre. Exemple, d'ailleurs très similaire à l'exemple choisi dans le paragraphe précédent :

local fouillis = {["Nourriture"] = "Fromage", ["Boisson"] = "Limonade", ["Bestiole"] = "Cafard", ["Couvert"] = "Fourchette", ["Truc"] = "Machin chose"}

function p.farfouille()
	local reponse = " "
	for index, objet in pairs(fouillis) do
		reponse = reponse.."<br />à la clé "..index.." se trouve l’objet "..objet.."."
	end
	return reponse
end

{{#invoke:Basique|farfouille}} nous retourne :
à la clé Nourriture se trouve l’objet Fromage.
à la clé Boisson se trouve l’objet Limonade.
à la clé Couvert se trouve l’objet Fourchette.
à la clé Truc se trouve l’objet Machin chose.
à la clé Bestiole se trouve l’objet Cafard.

Nous voyons que, cette fois, la table n’est pas lue dans l’ordre où on l'a écrit, ni dans l’ordre alphabétique.


Si la table contient, à la fois, des clés numériques et des clés sous forme de chaîne de caractère, la fonction pairs parcourra la totalité des clés, qu’elles soient sous forme numérique ou sous forme de chaîne de caractères.


Nous avons vu dans les chapitres précédents que l’on est souvent amené à présenter des données séparées par des virgules (paramètres d'une fonction, valeurs retournées par une fonction, remplissage d'une table indexée numériquement, etc.)

Si les données, que l’on veut séparer par des virgules sont rangées dans une table indexée numériquement, on peut les sortir et les présenter séparées par des virgules grâce à la fonction unpack.

unpack(table,i,j) sort les données d'une table indexée numériquement, de l'index i à l'index j et les présente séparées par des virgules.

Par exemple dans :

local nombres_premiers = {2,3,5,7,11,13,17,19,23,29,31,37,41}

function p.Calcul()
	local premier = {unpack(nombres_premiers,5,8)}
	etc.
end

La fonction p.calcul n'avait besoin que des nombres premiers entre 10 et 20. Elle a donc créer une table premier qui ne contient que les nombres premiers entre 10 et 20 extrait, grâce à la fonction unpack, de la table nombres_premiers. Les nombres premiers extraits ce trouvaient, dans la table nombres_premiers, de l'index 5 à l'index 8.


Nous ne sommes pas obligé de préciser les valeurs de i et j à l'appel de la fonction unpack. Par défaut i vaut 1 et j est égal au nombre d'éléments se trouvant dans la table.


La fonction select considère son premier argument et agit sur le reste de ses arguments en fonction du premier.

  • Si le premier argument est 1, la fonction select retourne le reste de ses arguments. Par exemple select(1,a,b,c,d) retournera a,b,c,d
  • Si le premier argument est 2, la fonction select retourne le reste de ses arguments sauf celui qui suit l'argument égal à 2. Par exemple select(2,a,b,c,d) retournera b,c,d
  • Et ainsi de suite. Par exemple select(3,a,b,c,d,e,f) retournera c,d,e,f. select(4,a,b,c,d,e) retournera d,e
  • Si le premier argument est #, la fonction select retournera le nombre de ses arguments sans compter le premier qui est #. Par exemple select(#,a,b,c,d,e,f,g,h) retournera 8


Cette fonction à deux paramètres est telle que rawget(tab,k) est équivalent à tab[k] (tab étant une table et k une clé). La seule différence est que la fonction rawget ignore une éventuelle méta-méthode associée au champ __index. Pour plus de précisions voir le chapitre sur les méta-tables


Cette fonction à deux paramètres est telle que rawequal(a,b) est équivalent à a == b (a et b étant des tables). La seule différence est que la fonction rawequal ignore une éventuelle méta-méthode associée au champ __eq. Pour plus de précisions voir le chapitre sur les méta-tables


Cette fonction à trois paramètres est telle que rawset(tab,k,v) est équivalent à tab[k] = v (tab étant une table, k une clé et v une valeur). La seule différence est que la fonction rawset ignore une éventuelle méta-méthode associée au champ __newindex. Pour plus de précisions voir le chapitre sur les méta-tables