Lua 的函数定义是没有参数类型信息的。这些信息在跨语言的模块化设计中非常有价值。因为跨语言的方法调用通常需要做列集(Marshaling) 的操作,缺乏类型信息很难完成这个工作。同样的需求在做 RPC 调用的时候也很重要。
感谢 Lua 简洁的 metatable 的设计,我们可以用一个简单的方法自然的描述出 Lua 函数的调用参数类型,又无损性能。
一开始,我们先回顾一下在 Pil 中推荐的类实现方法。通常我们可以把方法列表(对应于 C++ 中的虚表)放在一个 table 中,然后把这个 table 作为一个 metatable 的 __index 方法,并把 metatable 附加到一个 table 上,就生成了简单的对象。我喜欢这样做:
foo={}function foo:foobar(n) print(self) return n*nendfoo={__index=foo}function create(class,obj) return setmetatable(obj or {}, class) endt = 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 endend,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)endlocal 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, })endlocal meta_type={__index=function(t,k) return k end }local const_table = function (t,k,v) error "const table" endlocal 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 retend})function create(class, obj) return setmetatable(obj or {},class_vtbl[class])endlocal obj=create(test)print(obj:foobar(1,2))
以上的代码可以直接在 lua 解释器中运行:将输出结果
foobar table self number x number y string name table arg function: 003E7C38table: 003ED0D83
不多做解释了,弄明白原理的话,可以做更复杂的事情。比如模拟一个 enum,在函数定义里写 enum { "one","two","three" } 。或是增加返回值的描述,建议用语法:def(typename,typename,...).foobar(typename.arg1,typename.arg2)
甚至提供更强的,类似 COM 的接口描述:def.foobar(typename.in_out.arg1,typename.out.arg2)
云风 提交于 October 31, 2008 10:38 PM | 固定链接
请教一个问题:
function ( string str )
end
怎样改造lua,能支持这种方式? 有这种需求的原因是:
lua是弱类型,动态执行的,这时,在数十万行lua代码的项目中(数十人的团队), 参数在函数的多层调用中,想要知道是什么含义或取值或类型,已经很难追踪了.这是,可能需要如下一些特性:
变量要声明(类型也声明),不能随手使用,否则维护人员或版本迭代修改的开发人员,要吐血;
传参数要声明类型.
比如:
num = number;
num = 0;
str = string;
str = "hello";
function test ( string str, table tb1 )
--do something.
end
联系客服