Lua
Argumenty Variadic
Szukaj…
Wprowadzenie
Varargs , jak są powszechnie znane, umożliwiają funkcjom przyjmowanie dowolnej liczby argumentów bez specyfikacji. Wszystkie argumenty podane dla takiej funkcji są spakowane w pojedynczą strukturę znaną jako lista vararg ; który jest napisany jako ...
w Lua. Istnieją podstawowe metody wyodrębniania liczby podanych argumentów i ich wartości za pomocą funkcji select()
, ale bardziej zaawansowane wzorce użytkowania mogą wykorzystać strukturę do jej pełnej użyteczności.
Składnia
- ... - Sprawia, że funkcja, której lista argumentów zawiera funkcję variadic
- select (what, ...) - Jeśli „what” jest liczbą z zakresu 1 do liczby elementów w vararg, zwraca „what” element do ostatniego elementu w vararg. Zwrot będzie zero, jeśli indeks jest poza zakresem. Jeśli „what” jest ciągiem „#”, zwraca liczbę elementów w vararg.
Uwagi
Wydajność
Lista vararg jest implementowana jako lista połączona w implementacji języka PUC-Rio, co oznacza, że indeksy to O (n). Oznacza to, że iteracja elementów w vararg za pomocą select()
, podobnie jak w poniższym przykładzie, jest operacją O (n ^ 2).
for i = 1, select('#', ...) do
print(select(i, ...))
end
Jeśli planujesz iterację elementów na liście vararg, najpierw spakuj listę do tabeli. Dostęp do tabeli to O (1), więc iteracja to łącznie O (n). Lub, jeśli jesteś tak skłonny, zobacz przykład foldr()
z sekcji zaawansowanych zastosowań; wykorzystuje iterację do iteracji po liście vararg w O (n).
Definicja długości sekwencji
Vararg jest użyteczny, ponieważ długość vararg uwzględnia wszelkie jawnie przekazane (lub obliczone) zera. Na przykład.
function test(...)
return select('#', ...)
end
test() --> 0
test(nil, 1, nil) --> 3
To zachowanie jest jednak sprzeczne z zachowaniem tabel, w których operator długości #
nie działa z „dziurami” (osadzonymi zerami) w sekwencjach. Obliczanie długości stołu z otworami jest niezdefiniowane i nie można na nim polegać. Tak więc, w zależności od wartości w ...
, przyjmowanie długości {...}
może nie dać „poprawnej” odpowiedzi. W Lua table.pack()
wprowadzono table.pack()
aby poradzić sobie z tym brakiem (w przykładzie jest funkcja, która implementuje tę funkcję w czystym Lua).
Zastosowanie idiomatyczne
Ponieważ varargs noszą na swojej długości ludzie używają ich jako sekwencji, aby uniknąć problemu z dziurami w tabelach. To nie było ich zamierzone użycie, a referencyjna implementacja Lua nie jest dla niego optymalna. Mimo że takie użycie jest badane w przykładach, ogólnie jest źle traktowane.
Podstawy
Funkcje variadic są tworzone przy użyciu składni ...
elipsy na liście argumentów definicji funkcji.
function id(...)
return
end
Jeśli nazwiesz tę funkcję jako id(1, 2, 3, 4, 5)
to ...
(AKA lista vararg) będzie zawierać wartości 1, 2, 3, 4, 5
.
Funkcje mogą przyjmować wymagane argumenty, a także ...
function head(x, ...)
return x
end
Najłatwiejszym sposobem na pobranie elementów z listy vararg jest po prostu przypisanie do niej zmiennych.
function head3(...)
local a, b, c = ...
return a, b, c
end
select()
może być również użyty do znalezienia liczby elementów i wyodrębnienia elementów z ...
pośrednio.
function my_print(...)
for i = 1, select('#', ...) do
io.write(tostring(select(i, ...)) .. '\t')
end
io.write '\n'
end
...
można spakować do stołu dla łatwości użycia, używając {...}
. To umieszcza wszystkie argumenty w sekwencyjnej części tabeli.
table.pack(...)
można również wykorzystać do spakowania listy vararg do tabeli. Zaletą table.pack(...)
jest to, że ustawia pole n
zwracanej tabeli na wartość select('#', ...)
. Jest to ważne, jeśli lista argumentów może zawierać wartości zerowe (patrz sekcja uwag poniżej).
function my_tablepack(...)
local t = {...}
t.n = select('#', ...)
return t
end
Lista vararg może być również zwrócona z funkcji. Wynikiem jest wiele zwrotów.
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
Zaawansowane użycie
Jak podano w podstawowych przykładach, możesz mieć argumenty związane ze zmiennymi i listę argumentów zmiennych ( ...
). Możesz użyć tego faktu, aby rekurencyjnie rozdzielić listę, tak jak w innych językach (takich jak Haskell). Poniżej znajduje się implementacja foldr()
która z tego korzysta. Każde wywołanie rekurencyjne wiąże nagłówek listy vararg do x
i przekazuje resztę listy do wywołania rekurencyjnego. Spowoduje to destrukcję listy, dopóki nie będzie tylko jednego argumentu ( select('#', ...) == 0
). Następnie każda wartość jest stosowana do argumentu funkcji f
z wcześniej obliczonym wynikiem.
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
Inne definicje funkcji, które wykorzystują ten styl programowania, można znaleźć tutaj od numeru 3 do numeru 8.
Jedyną idiomatyczną strukturą danych Lui jest tabela. Operator długości tabeli jest niezdefiniowany, jeśli w dowolnym miejscu w sekwencji znajdują się nil
. W przeciwieństwie tabelach lista vararg szanuje wyraźny nil
s jak podano w podstawowych przykładów i uwag sekcję (proszę przeczytać tę sekcję, jeśli jeszcze nie). Przy niewielkim nakładzie lista vararg może wykonać każdą operację w tabeli oprócz mutacji. To sprawia, że lista vararg jest dobrym kandydatem do implementacji niezmiennych krotek.
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
Główną różnicą między powyższymi tabelami a tabelami jest to, że tabele są zmienne i mają semantykę wskaźnika, przy czym krotka nie ma tych właściwości. Dodatkowo krotki mogą zawierać jawne nil
i mieć operację o nieokreślonej długości.