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)