I’m familiar with numeric for loops and generic for loops, but the one thing that I can never understand is how exactly the iterators function. Chiefly, I don’t understand how string.gmatch’s for loops work.
for a in string.gmatch() do
How is this able to work without pairs, ipairs or next? Doesn’t it return a key-value table?
As for the aforementioned point about iterators, I don’t really understand them. I know that pairs and ipairs call next, but what’s the difference between using them and just calling in next? I know that pairs is for key-values and ipairs is for numeric indices-values, but can I not just skip those and use next?
And finally, for for loops in general, are there any conventions that can be stuck in here? Can you create your own iterator function and attach it to a for loop? The general convention seems to be:
for A in B do
So, from that, what can be placed in A and B respectively?
With regards to your final question, you certainly can create your own iterator function. Take this one example from lua-users wiki: Iterators Tutorial which allows you to iterate over the squares of a given number nbvals
The following iterator will return a sequence of squared values. When we return no value (i.e. n >= state), Lua returns nil which terminates iteration. Notice the iterator is returning the next value in the sequence for a given iteration. We use the state to hold the number of iterations we wish to perform.
-- state: The number of iterations to perform
-- n: a count of performed iterations
local function square(state, n)
if n < state then
n = n + 1
return n, n*n
end
end
-- Wrap the iterator in a function similar to pairs()
local function squares(nbvals)
return square, nbvals, 0
end
-- Calling the iterator function
-- calling square() directly would be:
-- for s, n in square,5,0 do ... end
for s, n in squares(5) do
print(s, n)
end
--[[
1 1
2 4
3 9
4 16
5 25
]]--
The above link goes into a lot more detail on custom iterators, including an ipairs implementation.
Thanks @school_month and @ScriptedForum for guiding me through this (happening now as this post is being made as well). I’m still open for responses though, since the concept isn’t fully down.
(If anything I’m about to say is incorrect please correct me I’d love to learn more about this)
So I thought I’d try to go a little more in-depth here since my discord explanation was all over the place.
The for generator wants a function to call whether it’s directly or from calling a function and that function returns another function (like pairs which returns next, ipairs, and string.gmatch). If there are any arguments next to the function it’ll pass those to it for the first call after that it passes the returns from the previous call. It’ll keep calling the function you passed until it returns nil.
** Also in addition I believe the generator only allows a maximum of 2 arguments to be added next to the function **
Between the for and the in is where your variables go, they’re the returns from the function call and they’re local to the do block.
A quick example function (functionally the same as ipairs):
local function myGenerator(tbl, index)
index = (index or 0) + 1
if tbl[index] == nil then return end
return index, tbl[index]
end
for index, value in myGenerator, {"a", "b", "c", 10, {}, false} do
print(index, value)
end
In the wiki, there is an example for these kinds of loops, which includes what string.gmatch probably does. Loops
local function gfind(stringToSearch, pattern, start)
local start = start or 1 -- Default value is 1
return function()
local beginning, ending = stringToSearch:find(pattern, start) -- Start searching at the specified location
if beginning and ending then -- Check to make sure that the match is there
start = ending + 1 -- Add one to the ending so the pattern will start to look after the last match
return beginning, ending, stringToSearch:sub(beginning, ending) -- return the 3 values
end
end
end
Something minor, but for in pairs loops only take into account the first two arguments passed after the function, not all.
for _ in print,1,2,3,4,5,6,7,8,9,10 do end
--> 1 2
If the first thing returned by the function is nil, then the loop breaks.
Otherwise, everything returned is passed.
for a,b,c,d,e,f in function()return 1,2,3,4,5,6 end do
print(a,b,c,d,e,f)
break
end
--> 1 2 3 4 5 6
The first thing returned by the function becomes the second argument next time the function is called.
As in, the first paramater will be static, while the second can change.
local h = 0
local function iter(x,y)
if h == 10 then return end
h = h + 1
return y+1,x+1
end
for a,b in iter,0,0 do print(a,b) end
--> 1 1
--> 2 1
--> 3 1
--> 4 1
--> 5 1
--> 6 1
--> 7 1
--> 8 1
--> 9 1
--> 10 1
Example useage:
local function iter(End,pos)
pos = (pos and pos + 1) or 1
if End >= pos then
return pos
end
end
for position in iter,5 do
print(position)
end
--> 1
--> 2
--> 3
--> 4
--> 5
A will be what is returned by B, as for B, the first thing should be a function, and the next two can be whatever you desire.
Explanation with how next works with the iterator-
next loops are done like this,
for i,v in next,t do
end
The first call will be next(t,nil) which gets the first thing to be iterated though with its key and value.
print(next{"first","second","third"})
--> 1 first
The next call takes into account the last key (next’s first return is the key, and as mentioned before, the first return becomes the second argument) and as will be next(t,lastkey)
print(next({"first","second","third"},1))
--> 2 second
This continues on until next returns nil, breaking the loop.
print(next({"first","second","third"},3))
--> nil
So it ends up like this
for i,v in next,{"first","second","third"} do
print(i,v)
end
--> 1 first
--> 2 second
--> 3 third
Expanding on this, it’s because Lua doesn’t think of these as “arguments”; the code generated marks them as “generator”, “state” and “control”.
for rets... in generator, state, control do
end
These are internally passed around in an optimized fashion (at least in vanilla Lua) which is why no more than 3 values are allowed, and any not provided are assumed nil.
It’s probably not very helpful to know, but gmatch basically creates a closure which holds its state index and match info within its upvalues with some magic. The function that it uses is always the same, though.