Yieldable For Loops

lua-users home
wiki

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 20, 2012 2:10 am GMT (diff)