Reputation: 347
I'm confused with the concepts of stateless iterators. As an exercise I write an iterator to print all non-empty substrings of a given string. The code is as follows.
local function iter(state, i)
local str = state.str
if type(str) ~= "string" or str == "" then return nil end
if state.ending > #str then return nil end
local start = state.start
local ending = state.ending
if start == ending then
state.ending = ending + 1
state.start = 1
else
state.start = start + 1
end
return string.sub(str, start, ending)
end
function allSubstrings(str)
return iter, { str = str, start = 1, ending = 1 }, nil
end
for substr in allSubstrings("abcd123") do
print(substr)
end
I use a table { str = str, start = 1, ending = 1 }
as the so-called invariant state, but I have to change the fields in this table in the iter
local function. So is this iterator stateless or with complex states? If it's not stateless, is there a way to implement this functionality with a stateless iterator?
Thank you.
Upvotes: 3
Views: 519
Reputation: 26355
This is not a stateless iterator, and is indeed an iterator with complex state.
In stateless iterators there is only one control variable, and it should be treated as a pure value throughout the iterator.
You could consider using closures to implement this. Not quite stateless, but does use upvalues over table lookups, which should be more efficient.
local function allSubstrings (str)
if type(str) ~= "string" or str == "" then
return function () end -- NOP out on first iteration
end
local length = #str
local start, ending = 1, 1
return function ()
if ending > length then return nil end
local st, ed = start, ending
if start == ending then
ending = ending + 1
start = 1
else
start = start + 1
end
return string.sub(str, st, ed)
end
end
for substr in allSubstrings("abcd123") do
print(substr)
end
Upvotes: 2
Reputation: 4271
The PIL-chapter about stateless iterators states:
As the name implies, a stateless iterator is an iterator that does not keep any state by itself. Therefore, we may use the same stateless iterator in multiple loops, avoiding the cost of creating new closures.
In code that means that both for
loops in:
f, s, var = pairs( t )
for k,v in f, s, var do print( k, v ) end
for k,v in f, s, var do print( k, v ) end
should produce the same output. This only works if neither the invariant state s
nor any upvalues of the iterator function f
change during the iteration, or else the second for
loop would have different starting conditions than the first loop.
So, no, your iterator is not a stateless iterator because you change the invariant state during iteration.
There is a way to make your iterator stateless (and the popular Lua library luafun
uses this technique extensively): keep all mutable state in the loop control variable var
(even if you need to allocate a fresh table in each allocation step):
local function iter( str, var )
if type( str ) ~= "string" or str == "" then return nil end
if var[ 2 ] > #str then return nil end
local start, ending = var[ 1 ], var[ 2 ]
if start == ending then
return { 1, ending+1 }, string.sub( str, start, ending )
else
return { start+1, ending }, string.sub( str, start, ending )
end
end
function allSubstrings2( str )
return iter, str, { 1, 1 }
end
for _,substr in allSubstrings2( "abcd123" ) do
print( substr )
end
Drawbacks are that the first iteration variable var
(the loop control variable) has no useful meaning for the user of the iterator, and since you have to allocate a table for each iteration step, "avoiding the cost of creating new closures" for another loop doesn't matter much.
luafun
does it anyway, because it does not have the ability to recreate an iterator tuple (it is passed via function arguments from outside code), and for some algorithms you absolutely need to run the same iteration multiple times.
An alternative to this approach would be to concentrate all mutable state in the "invariant state" s
(all upvalues to f
must be immutable), and provide a way to copy/clone this state for later iterations:
f, s, var = allSubstrings("abcd123")
s2 = clonestate( s )
for str in f, s, var do print( str ) end
for str in f, s2, var do print( str ) end
This is still not a stateless iterator, but it is more memory-efficient than the stateless iterator with heap-allocated loop control variable, and it allows you to restart an iteration.
Upvotes: 4
Reputation: 5857
PIL book calls that 'Iterators with Complex State'
http://www.lua.org/pil/7.4.html
Upvotes: 0