可 yield 的 for 循环

lua-users home
wiki

让 Lua 能够“任何地方都可以 yield”是(可能)可取的,但这是一个长期的项目。与此同时,我发现 for 循环中的迭代器函数不允许 yield 令人恼火;这使得编写简单的响应器循环变得很麻烦,例如,迭代器可能是一个异步输入函数。

与其只能够编写

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

你需要

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

然而,要使第一个代码示例工作非常简单。只需要将 `TFORLOOP` VM 操作拆分为两个操作码。第一个操作码设置一个普通的 Lua 调用,然后进入 OP_CALL 实现。接下来的操作码根据第一个操作码返回的第一个值进行条件移动和分支。

一些非常粗略的测试表明,这种改变实际上略微提高了性能,尽管结果并不确定。我想这是因为 VM 可以处理调用而无需递归,从而弥补了额外操作码的开销。

无论如何,补丁位于 [失效链接]

(也可以在这里找到 [1],取自 Google 代码搜索缓存 [2]。)

[针对 Lua 5.1.5 更新]

示例

这是一个测试程序。这里关键的函数是 `responder`,它展示了可 yield 的 `for` 循环的实际应用。测试输出紧随代码之后

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

示例运行

  
> 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


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2012 年 5 月 20 日凌晨 3:10 GMT (差异)