Lua
Arguments Variadic
Recherche…
Introduction
Les varargs , comme on les appelle communément, permettent aux fonctions de prendre un nombre arbitraire d'arguments sans spécification. Tous les arguments donnés à une telle fonction sont regroupés dans une structure unique appelée liste vararg ; qui est écrit comme ...
dans Lua. Il existe des méthodes de base pour extraire le nombre d'arguments donnés et la valeur de ces arguments à l'aide de la fonction select()
, mais des modèles d'utilisation plus avancés peuvent tirer parti de la structure pour son utilitaire complet.
Syntaxe
- ... - Fait la fonction dont la liste des arguments dans laquelle cela apparaît une fonction variadique
- select (what, ...) - Si 'what' est un nombre compris entre 1 et le nombre d'éléments du vararg, renvoie l'élément 'what'th au dernier élément de vararg. Le retour sera nul si l'index est hors limites. Si 'what' est la chaîne '#', retourne le nombre d'éléments dans vararg.
Remarques
Efficacité
La liste vararg est implémentée comme une liste liée dans l'implémentation PUC-Rio du langage, cela signifie que les index sont O (n). Cela signifie que l'itération sur les éléments d'un vararg en utilisant select()
, comme l'exemple ci-dessous, est une opération O (n ^ 2).
for i = 1, select('#', ...) do
print(select(i, ...))
end
Si vous prévoyez d'itérer sur les éléments d'une liste vararg, commencez par empaqueter la liste dans une table. Les accès à la table sont O (1), donc l'itération est O (n) au total. Ou, si vous le souhaitez, consultez l'exemple foldr()
de la section d'utilisation avancée; il utilise la récursivité pour parcourir une liste vararg dans O (n).
Définition de la longueur de séquence
Le vararg est utile dans la mesure où la longueur du vararg respecte tous les nils explicitement passés (ou calculés). Par exemple.
function test(...)
return select('#', ...)
end
test() --> 0
test(nil, 1, nil) --> 3
Ce comportement est en conflit avec le comportement des tables, où l'opérateur de longueur #
ne fonctionne pas avec des «trous» (nils intégrés) dans les séquences. Le calcul de la longueur d'une table avec des trous n'est pas défini et on ne peut pas s'y fier. Donc, en fonction des valeurs de ...
, prendre la longueur de {...}
peut ne pas donner la réponse "correcte" . Dans Lua 5.2+, table.pack()
été introduit pour gérer cette lacune (il y a une fonction dans l'exemple qui implémente cette fonction dans Lua pur).
Utilisation idiomatique
Parce que les varargs portent leur longueur, les gens les utilisent comme séquences pour éviter le problème avec des trous dans les tableaux. Ce n'était pas leur usage prévu et l'implémentation de référence de Lua n'optimise pas. Bien que cet usage soit exploré dans les exemples, il est généralement mal vu.
Les bases
Les fonctions variables sont créées à l'aide de la syntaxe ...
ellipses dans la liste d'arguments de la définition de fonction.
function id(...)
return
end
Si vous appeliez cette fonction comme id(1, 2, 3, 4, 5)
alors ...
(AKA la liste vararg) contiendrait les valeurs 1, 2, 3, 4, 5
.
Les fonctions peuvent prendre les arguments requis ainsi que ...
function head(x, ...)
return x
end
La méthode la plus simple pour extraire des éléments de la liste vararg consiste simplement à en attribuer des variables.
function head3(...)
local a, b, c = ...
return a, b, c
end
select()
peut également être utilisé pour trouver le nombre d'éléments et extraire des éléments de ...
indirectement.
function my_print(...)
for i = 1, select('#', ...) do
io.write(tostring(select(i, ...)) .. '\t')
end
io.write '\n'
end
...
peut être emballé dans un tableau pour faciliter son utilisation, en utilisant {...}
. Cela place tous les arguments dans la partie séquentielle de la table.
table.pack(...)
peut également être utilisé pour empaqueter la liste vararg dans une table. L'avantage de table.pack(...)
est qu'il définit le champ n
de la table renvoyée sur la valeur de select('#', ...)
. Ceci est important si votre liste d'arguments peut contenir des nils (voir la section des remarques ci-dessous).
function my_tablepack(...)
local t = {...}
t.n = select('#', ...)
return t
end
La liste vararg peut également être renvoyée par les fonctions. Le résultat est plusieurs retours.
function all_or_none(...)
local t = table.pack(...)
for i = 1, t.n do
if not t[i] then
return -- return none
end
end
return ... -- return all
end
Utilisation avancée
Comme indiqué dans les exemples de base, vous pouvez avoir des arguments liés aux variables et la liste des arguments variables ( ...
). Vous pouvez utiliser ce fait pour séparer récursivement une liste comme vous le feriez dans d'autres langues (comme Haskell). Vous trouverez ci-dessous une implémentation de foldr()
qui en tire parti. Chaque appel récursif lie la tête de la liste vararg à x
et transmet le reste de la liste à un appel récursif. Cela déstructure la liste jusqu'à ce qu'il n'y ait qu'un seul argument ( select('#', ...) == 0
). Après cela, chaque valeur est appliquée à l'argument de fonction f
avec le résultat calculé précédemment.
function foldr(f, ...)
if select('#', ...) < 2 then return ... end
local function helper(x, ...)
if select('#', ...) == 0 then
return x
end
return f(x, helper(...))
end
return helper(...)
end
function sum(a, b)
return a + b
end
foldr(sum, 1, 2, 3, 4)
--> 10
Vous pouvez trouver d' autres définitions de fonctions qui tirent parti de ce style de programmation ici dans le numéro 3 à numéro 8.
La seule structure de données idiomatique de Lua est la table. L'opérateur de longueur de la table n'est pas défini s'il y a des nil
s situés n'importe où dans une séquence. Contrairement à des tables, la liste vararg respecte explicitement nil
s comme indiqué dans les exemples de base et la section des remarques (s'il vous plaît lire cet article si vous n'avez pas encore). Avec peu de travail, la liste vararg peut effectuer toutes les opérations qu'une table peut avoir en plus de la mutation. Cela fait de la liste vararg un bon candidat pour l'implémentation de tuples immuables.
function tuple(...)
-- packages a vararg list into an easily passable value
local co = coroutine.wrap(function(...)
coroutine.yield()
while true do
coroutine.yield(...)
end
end)
co(...)
return co
end
local t = tuple((function() return 1, 2, nil, 4, 5 end)())
print(t()) --> 1 2 nil 4 5 | easily unpack for multiple args
local a, b, d = t() --> a = 1, b = 2, c = nil | destructure the tuple
print((select(4, t()))) --> 4 | index the tuple
print(select('#', t())) --> 5 | find the tuple arity (nil respecting)
local function change_index(tpl, i, v)
-- sets a value at an index in a tuple (non-mutating)
local function helper(n, x, ...)
if select('#', ...) == 0 then
if n == i then
return v
else
return x
end
else
if n == i then
return v, helper(n+1, ...)
else
return x, helper(n+1, ...)
end
end
end
return tuple(helper(1, tpl()))
end
local n = change_index(t, 3, 3)
print(t()) --> 1 2 nil 4 5
print(n()) --> 1 2 3 4 5
La principale différence entre ce qui précède et les tableaux est que les tables sont mutables et ont une sémantique de pointeur, où le tuple ne possède pas ces propriétés. De plus, les n-uplets peuvent contenir des nil
explicites et une opération de longueur jamais définie.