作者:糖果

LazyTable源码

local ngx_req = {
      headers = function()
          return "testcase"
      end,
      method = function()
          return "GET"
      end,
}


local lazy_tbl
lazy_tbl = function(tbl, index)
  return setmetatable(tbl, {
    __index = function(self, key)
      for k,v in pairs(index) do
          print(k, v)
      end
      --print(key)
      for k,v in pairs(self) do
          print(k, v)
      end
      local fn = index[key]
      if fn then
        do
          --此处fn(self)和fn(),传入self形参与否的效果是一样,table中的匿名函数
          --定义是没有参数的,这个形参是moonscript翻译后加上去的。
          local res = fn(self)
          --local res = fn(self)        
          local res = fn()
          self[key] = res
          --实际把return res去掉也不会影响这个程序的运行结果, 在此函数中
          --从了这个return语句再也没有return调用了,而设定工作在self[key] = res这句就已经完成。
          return res
        end
      end
    end
  })
end


local build_request
build_request = function(unlazy)
  if unlazy == nil then
    unlazy = false
    --unlazy = true
  end
  do
    local t = lazy_tbl({ }, ngx_req)
    if unlazy then
      for k in pairs(ngx_req) do
        --这里遍历的ngx_req,但是调用的函数是t的。
        local _ = t[k]
      end
    end
    return t
  end
end


req = build_request("unlazy")


for k,v in pairs(req) do
    print(k, v)
end

重置setmetatable

在一个匿名函数中,return setmetatable,通过对函数形参传入的table的变量的__index属
性进行统一修改,而新设定的__index对应函数的形参self和key,分别对应新table的本身,
和table对应的key。

__index = function(self, key)
 lazy_tbl({ }, ngx_req)

self:是ngx_req的引用。
key:是ngx_req的key。

如果发现,ngx_req的元素类型是function就执行下,然后,把返回结果(字符串)
替换原value值。

核心原理

lazy table实现的核心部分是,是在return setmetatable做table复制时,并统一的设定
新table的__index属性, 然后在遍历我们要批量设定的table时,得用table的__index对
应函数中的设置,修改要修改table的成员变量值,把table中,对应key的值是function
的元素,改成key的值等于,这个key对应匿名函数的返回值。

修改table元素的是由setmetatable指定的__index方法来完成。而遍历循环执行table中的
所有匿名函数,由一次外层的循环来完成。

moonscript的lua翻译特征

do end 结构是为了让local型的局部变量,在 do end 结构外不可见。

do 
   local tmp = 1
end 
print(tmp)

结果是:nil

do 
   tmp = 1
end 
print(tmp)

结果是:1

下面的代码有明显的moonscript翻译成lua的特征:

local test 
test = function()
    do  
        local tmp = "do end"
        return tmp 
    end 
    return "ret"
end

ret = test()
print(ret)

简化后的LazyTable代码

local ngx = { 
    url = function() 
        return "url"
    end,
    method = function()
        return "method"
    end,
}

local lazy

lazy = function(tbl, index)
    return setmetatable (tbl, {
        __index = function(self, key)
            print(key)
        end 
    })  
end


local build_request
build_request = function(unlazy)
        ret = lazy({}, ngx)
        for k in pairs(ngx) do
             local _ = ret[k]
        end
end

build_request("")

lazy的核心是通过lazy函数,重新设置table的__index对应函数
,在函数中调用元素值是function类型的函数,用函数返回结果修
改当前元素的value值。

通过LazyTable可把Table表的声明和元素值的动态设定在一个
封装调用周期内完成。

应用场景

LazyTable在应用在Lapis的获取nginx变量的一个功能,我们把这个功能
移到HiLua演示框架里,看LazyTable如何在实际应用场景应用的。

local ngx_request = {
  headers = function()
    return ngx.req.get_headers()
  end,
  cmd_meth = function()
    return ngx.var.request_method
  end,
  cmd_url = function()
    return ngx.var.request_uri
  end,
}

local lazy_tbl
lazy_tbl = function(tbl, index)
  return setmetatable(tbl, {
    __index = function(self, key)
      local fn = index[key]
      if fn then
        do  
          local res = fn(self)
          self[key] = res 
          return res 
        end 
      end 
    end 
  })  
end

return ngx_request

Moonscript是有OO的类的组织结构,但是用Moonscript写的Lapis框架,有的一些文件并没有
按类的形式组织,比如上面这个截取的代码,最后返回的就是一个ngx_request的table,通过
引用table中的函数类型元素进行调用,即可返回你想要的数据,这个类似之前lapis ES的写法
一个孙数的调用过程,可以整个组织成一个table声明的过程,在其过程中就完成了各种行为的
调用,看起来很整洁,调用时序隐含在声明里,而不是传统的若干个函数过程顺序调用的方式。

在一个数据结构中定义若干函数的调用,要是和路由器比较的话,路由器需要正则匹配判断
而这个是顺序执行。

在HiLua中的调用形式如下:

local req = require "nginx"

app:get("/hilua", function(request,id)
    ngx.say(type(req.cmd_meth))
end

nginx中ngx_request取得HTTP请求阶段的各种变量,这个数据结构在HiLua进行路由匹配处理
从这个数据结构中,取得相关变量。

local req = require "nginx"
function Route:run(router)
        local url = req.cmd_url()
        local method = req.cmd_meth()
end

LazyTable本身是一种设计实现的思路,可以用Moonscript实现,也可以用Lua实现,也可以
C/CPP实现。