# Type.lua **Repository Path**: jekit/Type.lua ## Basic Information - **Project Name**: Type.lua - **Description**: 一个纯 Lua 的类型系统 - **Primary Language**: Lua - **License**: MIT - **Default Branch**: master - **Homepage**: https://gitee.com/jekit/Type.lua - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-08-31 - **Last Updated**: 2023-10-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Type.lua 一只安安静静的 Lua 类型系统 ## 介绍 你看哈,Lua 本身是没有完整的对象系统的(继承那些) P.S. 那些继承都是用 metatable 模拟的 现在主流都是 Rich typed programming,也是 C++ 的目标,不要再像 C 那样,随便给你转。 所以,需要一个模块,能识别这个 `table` 是某 `table` `new` 出来的 现在吧,虽然 `30log` 能满足需求,但是呢,它的代码 是跟 `.min.js` 那样被压缩过了。 \ 基本都粘一坨了,它的名字 `30log` 也是为了 "30行" 而 "30行",所以你叫人咋看它代码吖…… 而且关键有一点,`30log` 是注入性的,require 了之后,基本就带了 class() 了,万一别人代码也有个 class() 呢。 \ 写代码就要考虑非注入式代码呀! 于是就有了 `Type.lua`,它为 简单学习 和 上手而生。 ## 使用 `Type.lua` 只要用 `Type.lua`, 你将获得以下特性: ### 1. 创建类、继承类和类型检查 咱们仪器检查一下 `b` 是个 `Base` 还有,检查 `d` 是个 `Derived` 类型的,同时它也是 `Base` 类型的(因为 Derive 继承了 Base 嘛)。 ```lua local Base = {} function Base.new() local o = {} -- 下面也可以不直接 return Type.set -- 如果你后面 还要做些初始化、在 返回对象给 -- 用户前 做些什么 操作的话 -- Type.set(o, Base) return Type.set(o, Base) end local b = Base.new() print(Type.is(b, Base)) -- 这里必打印 "true" print(Type.get(b) == Base) -- 这里也打印 "true" local Derived = {} -- 让 Derive 继承 Base Type.extends(Derived, Base) function Derived() local o = {} return Type.set(o, Derived) end local d = Derived.new() print(Type.is(d, Derived)) -- 这里必打印 "true" print(Type.is(d, Base)) -- 这里也打印 "true" ``` #### 1.1 继承 **你写不 extend 继承过来的话,那么就无法关联 Derive 跟 Base 的继承关系咯** ```lua --- Make the child type inherit to the parent type --- @param child table Child type --- @param parent table Parent type Type.extends = function (child, parent) ``` #### 1.2 设置类 **所有 {} 创建出来就是个普通的没有类信息的 table** **你要是不写 set 的话,辣莫,辣个变量就是个普通的 table 咯** ```lua --- Set object point to type --- @param obj table Instance object --- @param _type table Target type --- @param meta table|nil Metatable to be set --- @return table obj Configured object Type.set = function (obj, _type, meta) ``` ### 2. 初始化方法(可选) ```lua local Base = {} function Base.new() local o = {} -- 我在这里给 o 弄个变量 n o.n = 123 -- 设置类型,而不是直接返回 Type.set(o, Base) -- 设置完了类型,o 就有 Base 的方法了, -- 现在调用 init() 方法初始化一下 o:init() return o end function Base:init() print("Base:init()") end ``` ### 3. 运算符重载 你看哈,C++ 和 Ruby 都能随便的重载运算符啦,开发者都能个性化让两个 **相同** 或 **不同类型** 的对象 进行 "a + b" 具体怎么 `+`,运算符重载可以让你个性化魔改它。 C++ 用的 `T operator+(T const& obj)`,而 Ruby 是 `def +(obj)` 接下来看看 `Type.lua` 怎么做到的: ```lua local Base = {} function Base.new(n) local o = {} -- 初始化一个属性叫 n o.n = n return Type.set(o, Base, { add = function ( lhs, rhs, parent ) return Base.new(lhs.n + rhs.n) end }) end local b1 = Base.new(100) print("b1.n =", b1.n) -- b1 的 n 是 100 local b2 = Base.new(200) print("b2.n =", b2.n) -- b2 的 n 是 200 local b3 = b1 + b2 -- b1 + b2 并结果存给 b3 print("b3.n =", b3.n) -- b3 的 n 是 300 ``` 好啦,上面演示的 `add = function` 是重载了 `加法` 运算符,除了这个,`Type.lua` 现在支持 5 种类型的运算符哦! * `add` 加法 + * `sub` 减法 - * `mul` 乘法 * * `div` 除法 / * `concat` 连字符 .. 下面告诉大家这些写在 接口 长啥样的: #### 3.1 加法的接口 ```lua --- @param lhs any 前值 --- @param rhs any 后值 --- @param parent any 父类的 add 方法,你可以调上层父类的 add 噢 --- @return any 记得给个返回值 function add(lhs, rhs, parent) end ``` #### 3.1 减法的接口 ```lua --- @param lhs any 前值 --- @param rhs any 后值 --- @param parent any 当前对象的父类的 sub 方法 --- @return any 记得给个返回值 function sub(lhs, rhs, parent) end ``` #### 3.1 乘法的接口 ```lua --- @param lhs any 前值 --- @param rhs any 后值 --- @param parent any 当前对象的父类的 mul 方法 --- @return any 记得给个返回值 function mul(lhs, rhs, parent) end ``` #### 3.1 除法的接口 ```lua --- @param lhs any 前值 --- @param rhs any 后值 --- @param parent any 当前对象的父类的 div 方法 --- @return any 记得给个返回值 function div(lhs, rhs, parent) end ``` #### 3.1 连字符的接口 连字符 是用来 处理 `"123".."456"` 将两个字符串粘起来用的 ```lua --- @param str1 any 前值 --- @param str2 any 后值 --- @param parent any 当前对象的父类的 add 方法 --- @return any 记得给个返回值 function concat(str1, str2, parent) end ``` ### 4. Getter 和 Setter 例子:你是不是私藏了什么属性啊喂?! ```lua local Base = {} function Base.new(n) -- 我把 n 变量藏在 prop 局部(闭包)变量了 local prop = {n = n} local o = {} return Type.set(o, Base) end local b1 = Base.new(123) print("b1.n =", b1.n) -- nil,啊咧,n 都不出啊 ``` #### 4.1 Getter 的例子 来吧,整个 `get` 接口,这样把私有属性暴露一下吧 P.S. 接口 `get` 长这样的: ```lua --- @param t any 这个 t 就是 self --- @param k any Key,就是你写 `b1.n` 的时候,`k` 这时候就读入了 `n` --- @param parent any Self 的父类的 get 方法 --- @return any 记得给返回 function get(t, k, parent) end ``` 例子: ```lua local Base = {} function Base.new(n) -- 跟上面一样,初始化私有变量在这 local prop = {n = n} local o = {} return Type.set(o, Base, { get = function ( t, k, parent ) -- 就是你写 `b1.n` 的时候,`k` 这时候就读入了 `n`,这时候 `k` == `n` -- 所以返回 prop["n"] 嘛,所以写 prop[k] return prop[k] -- 但大多数情况咧,你这样只返回了 你自己私藏的 prop.n, -- 你爹、你爸爸 还有个 n 咋办咧? -- 所以你要把参数 传给你爹,就是父类的 get,也就是 parent -- 让他把他私藏的 n 也返回了,不然就只有你私藏的了 -- P.S 还可能有爷爷的...(捂脸) -- -- return prop[k] or parent(t, k, p) end }) end local b1 = Base.new(123) print("b1.n =", b1.n) -- 打印 123 ``` #### 4.2 Setter 例子 好啦,有了 **读取** 私有属性之后,还有 **设置** 私有属性的方法啦 接口 `set` 长这样的: ```lua --- @param t any 这个 t 就是 self --- @param k any Key,就是你写 `b1.n` 的时候,`k` 这时候就读入了 `n` --- @param v any Value,就是你写 `b1.n = 10086` 的时候,`v` 这时候就是 `10086` function set(t, k, v, parent) end ``` Example: ```lua local Base = {} function Base.new(n) -- Init private member here local prop = {n = n} local o = {} return Type.set(o, Base, { get = function ( t, k, p ) return prop[k] or parent(t, k, p) end, set = function ( t, k, v, p ) -- 这里写个 and 是为了避免 k 不在你私藏的 prop 里面啦 -- 否则的话,别人写个 `b1.fk = 123` 就给你的 `prop` 加个 `fk` 进去啦 prop[k] = prop[k] and v -- 跟上面一样,把参数给父类,让父类也设置一下嘛~ -- 但是!!!如果你想让类做成 protected,那就可以不写 -- 只能改自己的属性,父类的改不了 -- 主要看你设计 p(t, k, v, p) end }) end local b1 = Base.new(123) print("b1.n =", b1.n) -- 打印现在的 b1.n 是 123 b1.n = 10086 print("b1.n =", b1.n) -- 打印现在的 b1.n 是 10086 ``` ## 问题答疑 问: `Type.lua` 是 `元表式 OOP` 还是 `闭包式 OOP` 答: 混合滴. 有疑问有问题发 Issue 谢谢 ## 许可证 本模块属 `Jakit Liang` (泊凛) 个人原创 模块使用 MIT-Licensed 协议,禁止盗版!