Yieldable For Loops


Changing Lua so that "anything can yield" is (probably) desirable, but it's a long term project. In the meantime, I find it irritating that the iterator function in a for loop is not allowed to yield; it makes it messy to write simple responder loops where the iterator might be, for example, an asychronous input function.

Instead of just being able to write:

for msg in eachmsg() do
 -- handle msg
end
-- end of messages, clean up

you need:

repeat
 local msg = getmsg()
 if msg == nil then break end
 -- handle msg
until false
-- end of messages, clean up

However, it is very simple to get the first code sample to work. It is only necessary to split the TFORLOOP VM operation into two opcodes. The first one sets up an ordinary Lua call and then falls into the OP_CALL implementation. The following op code does a conditional move and branch based on the first value returned by the first op code.

Some very rough testing seems to show that performance is actually slightly improved by this change, although the results are not definitive. I suppose that is because the VM can handle the call without recursing, making up for the overhead of an extra opcode.

At any rate, the patch is at [dead link].

(Also available here[1], taken from Google Code Search cache[2].)

([Updated for Lua 5.1.5])

Example

Here's a test program. The key function here is responder, which shows the yieldable for in action. Test output follows the code

local yield, resume, create, costatus =
 coroutine.yield, coroutine.resume, coroutine.create, coroutine.status
 
local function input(prompt)
 local inf, outf = io.stdin, io.stderr
 return function()
 outf:write(prompt," ")
 return inf:read()
 end
end
 
-- These could be quite a bit more complex
function eachmsg()
 return yield
end
 
-- This isn't actually used in this demo, but it could be :)
getmsg = coroutine.yield
-- This would probably be more complicated in a real app, too. 
function responder(name)
 local n = 0 
 print(name.." is born!")
 for msg in eachmsg() do
 n = n + 1
 if msg == "goodbye" then break
 else print(name.." heard "..msg)
 end
 end
 print(name.." departs this vale of tears, after listening to "..n.." utterances")
end
 
function driver()
 local cmd = {}
 local kids = {}
 -- the commands we understand
 function cmd.quit() 
 print "Exiting!"
 for _, kid in pairs(kids) do
 resume(kid)
 end
 return false
 end
 function cmd.kill(arg)
 local _, _, who = string.find(arg, "(%w+)")
 if not who then
 return "Kill who?"
 elseif not kids[who] then
 return who.."? I don't know any "..who
 else
 local status, result = resume(kids[who])
 kids[who] = nil
 if status then
 return
 else
 return result
 end
 end
 end
 function cmd.spawn(arg)
 local _, _, who = string.find(arg, "(%w+)")
 if not who then
 return "Spawn who?"
 elseif kids[who] then
 return who .. " already exists"
 else
 kids[who] = create(responder)
 local status, result = resume(kids[who], who)
 if not status then
 kids[who] = nil
 return result
 end 
 end
 end
 function cmd.list()
 print"Currently active:"
 for k in pairs(kids) do print(" "..k) end
 end
 
 -- main loop starts here --
 for msg in input("->") do
 local _, _, verb, rest = string.find(msg, "%s*(%w+)%s*(.*)")
 if cmd[verb] then
 local res = cmd[verb](rest)
 if res then print(res)
 elseif res == false then return
 end
 elseif kids[verb] then
 local status, result = coroutine.resume(kids[verb], rest)
 if not status then
 print(verb.." exited with error "..result)
 kids[verb] = nil
 elseif coroutine.status(kids[verb]) ~= "suspended" then
 print(verb.." decided to go away")
 kids[verb] = nil
 end
 else
 print "I don't understand what you're talking about"
 end
 end
end

Sample run:

 
> driver()
-> list
Currently active:
-> spawn bob
bob is born!
-> spawn sally
sally is born!
-> bob hi
bob heard hi
-> sally hi
sally heard hi
-> bob meet sally
bob heard meet sally
-> fred hi
I don't understand what you're talking about
-> spawn fred
fred is born!
-> list
Currently active:
 sally
 fred
 bob
-> fred how are you
fred heard how are you
-> fred goodbye
fred departs this vale of tears, after listening to 2 utterances
fred decided to go away
-> kill bob
bob departs this vale of tears, after listening to 2 utterances
-> sally ?
sally heard ?
-> spawn sue
sue is born!
-> quit
Exiting!
sally departs this vale of tears, after listening to 2 utterances
sue departs this vale of tears, after listening to 0 utterances

-- RiciLake


RecentChanges · preferences
edit · history
Last edited May 19, 2012 9:10 pm GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /