想法和思路

在最近学习lua面向对象的时候,在想可不可以为一个类实现多个构造函数或者是new,也就是传统意义上的函数重载。尽管没有什么没有什么实际用途,毕竟想要实现同名函数的不同入参,只要入参的表内容不同即可。于是,这个问题就变成了有没有什么办法可以在Lua中从形式上实现和静态语言(比如C++)类似的函数重载。

一开始思考的时候,根据lua面向对象的方式,会考虑使用元表。即:如果根据条件判断出_当前的ctor函数或者new函数的入参形式和传参不同,就向metatable中的__index检索。然后我就有些犹豫,虽然很难会出现有10个以上的ctor或者new函数的情况,但如果出现了,那不是就会变成__index检索到__index这样不停迭代的情况吗,难道真的要这样实现?也许是我思考的方向不对。

于是我就换了一种思路,比如:是都存在一个表中,当要调用的时候遍历该表,用以查找同入参的函数。为了尽可能地减少搜索时间,表可以使用入参从多到少或从少到多排序,同数量的入参再根据类型排序。但这样的实现方式,依旧存在问题:

如果number类型在同参数数量根据类型继续排序时,根据类型(假如类型的先后是根据类型首字母的ASCII码大小)排在了string之前,那么后者要花费的检索时间要比前者长。这种情况也很麻烦,所有的函数应该是平级的。那么,如果想要平级,就需要在检测到第一个参数类型的时候,去第一个参数类型所存的后续列表中查找,类似于散列的思想,在第一个参数和第二个参数成功找到之后,再在第二个参数拥有的第三参数列表中检索第三个参数。使用这种一个表作为另一个表的索引的形式,并依次迭代下去。

想到这里的时候,我隐隐约约感觉哪里不对,(:з」∠),恩……似乎和使用元表不停的迭代__index没有区别了(某种意义上,似乎加深了我对元表的理解)。于是,最后决定试着用元表去实现Lua的函数重载,函数的索引也顺理成章地选择使用每个入参的参数类型。
那么,怎么样才能实现函数名与函数入参类型在定义时自己注册进元表中呢,使用__newindex吗?如果是的话,又要怎么样一层一层迭代地注册进元表中呢?我在这里卡住了。一通查找后,我在wiki上发现了这篇文章:
Overload Functions lua users wiki
于是,我模仿wiki上的写法,实现了一个函数重载的脚本。

代码实现

local OverloadModule={}

-- private
local funcList={}
local mt={}

-- 作用: 错误处理
local function perror()
    return error("Invalid argument types to overload function.")
end

--作用:(相同函数名+入参不同)的调用触发在funcList中依据入参列表的类型查找有无对应函数
function mt:__call(...)
    local default=self.default
    local paramTypeList={} --用于存放入参类型的列表
    for i,param in ipairs({...}) do
        paramTypeList[i]=type(param)
    end
    paramTypeList=table.concat(paramTypeList,",")
    return (funcList[paramTypeList]or self.default)(...)
end

-- 作用:多个入参重载时,由于前面的索引是通过检索操作触发的,会进入__index函数
function mt:__index(key)

    local paramTypeList={} --用于存放入参类型的列表

    -- 多参数配置时,最后一个参数的入口,因为最后一个索引操作相当于是在给该索引赋值一个函数
    -- 该函数不能写在__index下面,因为__index里要注册该函数为最后一个入参的赋值函数
    local function __newindex(self,key,value)
        print("\nThe Last param is "..key..". No Same input-params-func in List,Add a new-Input-Param-Types-function.")
        paramTypeList[#paramTypeList+1]=key
        funcList[table.concat(paramTypeList, ",")]=value --将该入参列表设置为当前注册的函数
        print("Overload a new function with input-params are:".."("..table.concat(paramTypeList, ",")..")")
    end

    -- 多参数配置时,除最后一个参数外的入口,因为设置前面参数的操作本质上是索引self
    local function __index(self,key)
        print("\nSearching "..tostring(self).." with next param type is "..key)
        -- 存储当前的入参,然后为其申请它的next-param查找列表,检索方式和现在相同,所以为其配置同样操作的元表
        paramTypeList[#paramTypeList+1]=key
        local nextParamSearchTable={}
        print("and Next Search Table is "..tostring(nextParamSearchTable))
        return setmetatable(nextParamSearchTable,{__index=__index,__newindex=__newindex})
    end
    return __index(self,key)
end
-- 作用:单个入参重载时,直接设置key,value
function mt:__newindex(key,value)
    funcList[key]=value
    print("Overload a new function with input-params are:".."("..key..")")
end

-- public

-- 作用:new函数返回一个可以进行重载功能的表
function OverloadModule:new()
    return setmetatable({default=perror}, mt)
end

return OverloadModule

代码验证

然后是简单的测试和输出结果。(配置为MacOS10.14.2+Sublime3.1.1+Lua5.1)

local overloader=require("OverloadModule")

local newItem=overloader:new()

function newItem.table(item)
    print("Creating by copy a item.")
end

function newItem.string(name)
    print("Create a item by name.")
end

function newItem.string.boolean.number.string(name,canSell,price,descrp)
    print("Create a item by name and its price(if can be sold) with description.")
end

print("\n")
newItem("MagicBook",true,100,"This is a MagicBook.")

Output

Overload a new function with input-params are:(table)
Overload a new function with input-params are:(string)

Searching table: 0x7faa7ec0b460 with next param type is string
and Next Search Table is table: 0x7faa7ec03150

Searching table: 0x7faa7ec03150 with next param type is boolean
and Next Search Table is table: 0x7faa7ec0b730

Searching table: 0x7faa7ec0b730 with next param type is number
and Next Search Table is table: 0x7faa7ec0b950

The Last param is string. No Same input-params-func in List,Add a new-Input-Param-Types-function.
Overload a new function with input-params are:(string,boolean,number,string)


Create a item by name and its price(if can be sold) with description.
[Finished in 0.0s]

小结

这样就实现了一个item类的使用item对象构造,使用名称构造,使用item字段构造(第三个完全可以传一个表进入,所以才说这个功能完全没有用啊,只是练习了使用元表而已)等多种构造函数的Lua函数重载。



Lua C

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!