Luaのはまりどころ

マイクロスレッドの比較対象のひとつとしてlua5.1を使ってみたけど、Luaって最近の言語と比べて結構独特な部分が多いなと思いました。以下、はまったところ:

  • Tableを配列風に使うときの開始インデックスは1であることを忘れる
  • Tableの存在しないインデックスの値や未定義変数の値もnilが返る
    • nilを値として使いにくい(ls = {nil, nil}; print(#ls) --=> 0)
  • cls.method(self) と書くところを cls:method(self) と書いても、その間違いに気がつきにくい
  • localを書き忘れる


coroutine用channel実装と、生産者消費者例

-- many-to-many channel
Channel = {}
function Channel:new()
   local obj = setmetatable({}, {__index = self})
   obj:init()
   return obj
end
function Channel:init()
   self.paused_sender = setmetatable({}, {__index = table})
   self.paused_receiver = setmetatable({}, {__index = table})
   self.msg = nil
end
function Channel:send(msg)
   if self.msg ~= nil then
      -- pause sender when buffer queue filled up
      self.paused_sender:insert(coroutine.running())
      coroutine.yield()
   end
   self.msg = msg
   if #self.paused_receiver > 0 then
      -- resume paused receiver
      local paused = self.paused_receiver:remove(1) -- lua index start with 1
      coroutine.resume(paused)
   end
end
function Channel:recv()
   if self.msg == nil then
      -- pause receiver when buffer queue empty
      self.paused_receiver:insert(coroutine.running())
      coroutine.yield()
   end
   local msg = self.msg
   self.msg = nil
   if #self.paused_sender > 0 then
      -- resume paused sender
      local paused = self.paused_sender:remove(1)
      coroutine.resume(paused)
   end
   return msg
end

-- examples
prod = coroutine.wrap(
   function (ch)
      print("prod start")
      for i = 1, 10 do
         ch:send(i * 2)
      end
      ch:send(-1)
      print("prod end")
   end)

cons = coroutine.wrap(
   function (ch)
      print("cons start")
      while true do
         local msg = ch:recv()
         if msg == -1 then
            break
         end
         print("rcv: " .. msg)
      end
      print("cons end")
   end)

ch = Channel:new()
prod(ch)
cons(ch)

幾分冗長な実装です。yield/resumeではパラメータを受け渡し可能なので、工夫すればmsgは不要になるでしょう。queueも実質ひとつでよく、senderかreceiver化どちらかをため続けるようにすればいいはず。