« 听说支付宝已经可以在 Linux 下用了 | 返回首页 | 不要拒绝学习 »

给 Lua 增加参数类型描述

Lua 的函数定义是没有参数类型信息的。这些信息在跨语言的模块化设计中非常有价值。因为跨语言的方法调用通常需要做列集(Marshaling) 的操作,缺乏类型信息很难完成这个工作。同样的需求在做 RPC 调用的时候也很重要。

感谢 Lua 简洁的 metatable 的设计,我们可以用一个简单的方法自然的描述出 Lua 函数的调用参数类型,又无损性能。

一开始,我们先回顾一下在 Pil 中推荐的类实现方法。通常我们可以把方法列表(对应于 C++ 中的虚表)放在一个 table 中,然后把这个 table 作为一个 metatable 的 __index 方法,并把 metatable 附加到一个 table 上,就生成了简单的对象。我喜欢这样做:

foo={}

function foo:foobar(n)
    print(self)
    return n*n
end

foo={__index=foo}

function create(class,obj)
    return setmetatable(obj or {}, class)       
end

t = create(foo) -- 构造一个 foo 对象
t:foobar(100)   -- 测试 foobar 方法

当然还有很多看起来更 cool 、更 OO 、更 C++ 的方法来在 Lua 下模拟一个类机制。云风以前也做过一个 ,其实后来还做过,或是见过身边认识的人做的“更漂亮”。只是请小心:Lua 不是 C++ 。实现一个超级 OO 的 Lua 代码风格跟解决问题是两件事,切莫迷失在这些语言技巧中。

有时候我喜欢这样定义成员函数:

local foo={}

setfenv(function ()
    function foobar(self,n)
        print(self)
        return n*n
    end
end,foo)()

for _,v in pairs(foo) do setfenv(v,_G) end

利用一个匿名函数,设置独立的 table 做为环境,有时可以方便许多。写起来更自由一些。这种利用独立环境的技巧有时也用来模拟类似 pascal 中的 with

下面是我们的正题:我们可以在此基础上给函数定义加上参数类型申明。

看起来函数定义的代码是这样的:

setfenv(function ()

    def.foobar(table,number,
    function (self,n)
        print(self)
        return n*n
    end)

end,foo)()

这次的函数定义是用 def.function_name 达到的,并且把类型信息独立一行来写。第一行的类型名(table,number),第二行才是参数名(self,n)。

def table number string 这些可以在环境中定义好,作为这套体系中的保留字。它们可以为参数类型系统服务。def 可以是带元方法的空表,通过 __index 元方法捕获 . 后面的字符串参数。这样: 让 del.foobar 产生一个函数用来在环境中设置一个方法,并记录正确的类型信息(供别的框架设施使用)。如果你想在调试期做更强的类型检查,可以给注册的函数 function (self,n) 加一个壳,检查每个传入参数的类型是否匹配。甚至可以实现 Eiffel 那样的调用契约。


云风在下面给出一个简单但完整的实现。(比上面更多出记录了参数名字,语法是 def.foobar(table.self,number.n, 这些信息对于模块的方法自提供文档很有好处)

function test()
    def.foobar(table.self,number.x,number.y,string.name,table.arg,
    function(self,x,y,name,arg)
        print(self)
        return x+y
    end)
end

local meta_arg_tostring={
    __tostring=function (t) return t[1].." "..t[2] end
}

local function make_type(typename)
    return setmetatable({typename},{
        __tostring=function(t) return typename end,
        __index=function(t,k) return setmetatable({typename,k},meta_arg_tostring) end,
    })
end

local meta_type={__index=function(t,k) return k end }
local const_table = function (t,k,v) error "const table" end

local type_gen=setmetatable({
    number=make_type "number",
    boolean=make_type "boolean",
    string=make_type "string",
    table=make_type "table",
    def=setmetatable({},{
        __index = function (t,k)
            return function(...)
                local vtbl=getfenv(3)
                vtbl.__meta[k]=arg
                vtbl[k]=setfenv(arg[#arg],_G)
                print(k,unpack(arg))    -- 输出函数的原型信息
            end
        end,
        __newindex = const_table
    })
},{__index=_G,__newindex = const_table } )

local class_vtbl = setmetatable ({},{__index=
function (t,k)
    local ret={ __meta={} }
    local gen=setfenv(k,type_gen)
    setfenv(function () gen() end,ret)()
    ret={ __index=ret }
    t[k]=ret
    return ret
end})

function create(class, obj)
    return setmetatable(obj or {},class_vtbl[class])
end

local obj=create(test)
print(obj:foobar(1,2))

以上的代码可以直接在 lua 解释器中运行:将输出结果

foobar  table self      number x        number y        string name     table arg       function: 003E7C38
table: 003ED0D8
3

不多做解释了,弄明白原理的话,可以做更复杂的事情。比如模拟一个 enum,在函数定义里写 enum { "one","two","three" } 。或是增加返回值的描述,建议用语法:def(typename,typename,...).foobar(typename.arg1,typename.arg2)

甚至提供更强的,类似 COM 的接口描述:def.foobar(typename.in_out.arg1,typename.out.arg2)

Comments

请教一个问题: function ( string str ) end 怎样改造lua,能支持这种方式? 有这种需求的原因是: lua是弱类型,动态执行的,这时,在数十万行lua代码的项目中(数十人的团队), 参数在函数的多层调用中,想要知道是什么含义或取值或类型,已经很难追踪了.这是,可能需要如下一些特性: 变量要声明(类型也声明),不能随手使用,否则维护人员或版本迭代修改的开发人员,要吐血; 传参数要声明类型. 比如: num = number; num = 0; str = string; str = "hello"; function test ( string str, table tb1 ) --do something. end
to sboy: 我现在一直沉浸在考研中,不去想其他事情就是怕自己会中途放弃,真正的考研结果如何,我不去想。奋斗过,努力过,我就可以问心无愧,然后勇敢的面对以后的路。暂时进不了网易不代表以后进不了,只要努力,我相信还是有希望的!很喜欢《老人与海》中的坚强老人,可以被打败,但是不能被打倒!
用lua完成一个类型检查确实很简单,不知道用lua完成一个lua编译器会怎样!呵呵!
to wild: 你跟我是一样的想法啊!只可惜我考研一半找到工作了,不考研了。看来以后是没法进网易了。 研究生期间努力进入网易,也是很难,哎,对我来说,要求太高了。
最近忙乎着考研,闲暇之时也看看云风大哥的博客和那本《我的编程感悟》,很想进网易,做个游戏设计师,可惜大学3年迷迷糊糊度过了。哎,往者不可谏,来者犹可追。只能把握机会考研成功,在研究生期间努力,争取进入网易,见到云风大哥。^_^
云风老大能就Lua中如何提高性能写点指导么?
最近经济不景气~ 人也少了
天天看你的BLOG,今天才更新,今天喝得有点醉.不好意思,不知道想说什么
只为沙发,哈哈

Post a comment

非这个主题相关的留言请到:留言本