Sucha's Blog ~ Welcome

20年9月19日 周六 23:57

Object Storage 和 Shell 环境变量

找了几个 Object Storage 的方案,理想中的可以是去中心化的方案,然而找不到,找到的要么是 IPFS,要么是链圈的,太高大上了。退而求其次,分布式的的方案,就很多了,各大云商家的对象存储,其实蛮便宜的,还有基于 minio 的,基于 Redis Master-Slave 的方案,及其变种的也算一类,还有豆瓣的 gobeandb,以及 bitraftlf,一个去中心化的 Key/Value Store.

因为有云服务器了,单独又开对象存储不大值得,因为需要存的东西不多,只是希望至少是分布式的,多个保全而已;minio 的方案,在分布式使用到时候,需要占用单独的磁盘,这个只好放弃了;Redis 及其变种 SSDB 之类的,底层 levelDB,或者占用内存多的放弃了,因为读写的频率很低,而且需要跟其他程序一起占用小排量的云服务器;gobeandb 犹犹豫豫很想用,但需要单独使用一个 goproxy 来分流感觉不大值得,接口是 memcached 的,手头也没有立即能用的解析器;bitraft 试用了一下,感觉最为接近,接口是 Redis 的,但是,无法基于不同数据中心做同步,我的两台云服务器位于不同的服务商它居然无法连接,就不说他默认 value 只有 64k 了;lf 一样没有趁手的接口,而且感觉不好配置。

bitraft 是最接近能用的,而且本地验证同步是很不错的,可惜了。

这里衬托了这么久,只是为了说自己最后写了一个,底层存储基于 Tokyo Cabinet,有现成的 Lua 绑定,网络服务接口用的是 rpc_framework 搭建的,同样基于 Redis 协议 lua-resp,同步方案现在很挫的了,一个 master,slave 主动同步 master,启动的时候同步全部 key(也许后面可以优化),然后每隔 30 秒同步一次最新的 SET/DEL 操作;master 这边是每 15 秒区分一个操作 group,操作 group 里面记录所有的 SET/DEL 操作,有 slave 请求,就将 group 时间点及其之后的所有分组 group 操作同步过去;slave 每次都同步上次最新 group 时间点之后的操作数据。

如果 master 这边留了足够多时间跨度的操作 group,后续即便偶尔连不上也是可以保证接下来的数据的;不过目前确实没有考虑 slave 间隔读取不到 master 操作的问题。

为了安全,还加上了 AUTH 命令,简简单单,基本能用了,VALUE 的长度,跟 REDIS 一样是 512M,基本够用了。

Tokyo Cabinet 据说在 2kw 左右的数据量后,会读写缓慢,现在距离这个点还早得很,而且很可能都遇不上。更好的,当然是使用 bitcast 做底层存储,可惜目前也没有方案。

先这样吧,代码就不公开了,单机版在 rpc_framework apps 里做 demo 了。

--

方案找了好久,差不多一周,试用了 bitcast 半天,最后决定用自己的半成品,大概写了 2 天,郁闷的是后面半天时间,每次自己手动启动就正正常常的,但是使用服务推上去就跑不起来,slave 变成了 master。

后来发现,是接受推送,拉起服务的 service,没有使用到包含最新区分不同云服务商的 shell 变量,所以 fork 后拉起来的服务,自然就缺少这个 shell 变量了;手动启动正常,是因为 SSH 进去后每次都读取最新的 bash 配置。

囧啊。

20年8月17日 周一 23:32

LuaJIT 下的双链表及 AVL 树

Lua 描述结构数据方面,只有一种内建结构,就是 table,table 既是 array,又是 hashmap,算是简洁高效的实现,但我们需要一种动态表示元素先后、大小关系的数据结构时,内建的 table 就不合适了,比如常见的 fifobinaryheap

先说优点,上面两个都是使用 table 来描述先后顺序、大小关系的,缺点是,当有插入、删除发生时,时间效率太慢了。其中 fifo 是使用 table 的 array 实现的,需要在删除元素后,对其他的元素做拷贝迁移,避免空位;而 bianryheap,也是使用使用 table 的 array,当元素位置变动时,采用了冒泡算法,对元素重新排位。

空间效率是保住了,但就时间效率来说,太不专业了,O(N^2) 的实现,数据量稍微多一点点,时间不能看。

其实 fifo 算是一种双链表,只是插入是在头尾,算是双链表的特例。双链表下,非头尾的删除发生,时间效率是 O(1) 级别的。其实可以用 table 来描述链表关系,缺点是空间消耗大,为了描述两个相邻元素间的先后顺序,就得用一个 table 实例来做。

我用 Lua 内建的 table 实做了一把,时间效率是很高效的,空间的话,插入 number,10,000,000 级别的插入,内存大概 6G。

-- performance
push 1000,000, cost 0.393088
pop  1000,000, cost 0.092984

在 C 的双链表实现中,需要将链表节点跟数据结构绑定起来,要么是以链表节点为主,链表节点指向数据结构,或者像 Linux Kernel 里面规范化的双链表实现,链表节点实例,放在数据结构中,通过偏移定位数据起点。

但是在 Lua 里面做不到这样,不得不使用 value(值)到 node(节点),以及 node 到 value 的两个 table 来做关系映射,再加上相邻两个 value 的顺序关系使用一个 node(table)来实现,也是空间消耗大的原因。

LuaJIT 下 cdata 的妙用

LuaJIT 因为有 FFI,可以创建结构化的 cdata 来描述相邻 node 之间的关系,而不需要使用内建的 table,使得可以花费更少的空间代价,比如下面这样来描述双链表的节点关系:

ffi.cdef [[
struct _cnode {
    struct _cnode *prev;
    struct _cnode *next;
    float key;
};
struct _chead {
    struct _cnode *head;
    struct _cnode *tail;
};
void* calloc(size_t count, size_t size);
void free(void *ptr);
]]

特别注明 cdata 是 calloc 出来的,不在 gc 管理范围,因为 C 下面的指针,需要保证指向固定的内存地址。其他跟纯 table 实现的双链表一样,需要两个 table 来维护 value 跟 node之间的映射关系,如上面 struct _cnode 下的 key,其实是用于 node 到 value 的 table 的映射 key,原因下面再说。

同样的 AVL 树也可以使用 cdata 来维护节点间的大小关系:

ffi.cdef [[
struct _avl_node {
    struct _avl_node *left;
    struct _avl_node *right;
    struct _avl_node *parent;
    int height;
    float key;
};
struct _avl_root {
    struct _avl_node *node;
};
void* calloc(size_t count, size_t size);
void free(void *ptr);
]]

以上使用 cdata 描述元素关系时间效率跟纯 table 时间效率一样,空间效率的话,第一个例子下,内存占用不到 4G,地址 ffi_list.luaffi_avl.lua

一些问题

LuaJIT 下使用 cdata 描述节点关系,遇到了一些问题:

上面的问题,估计还是要设计一些测试用例来定位一下才好,现在先这样吧。

后记

19 号解决了上面遇到的 2 个问题,原因在于 ffi.cdef 使用的 key 宽度是 float,但是 LuaJIT number 的宽度是 double 导致的,我修改 ffi.cdef node 里面的 key 为 double 类型,问题不再出现。

不过我也有疑问,为何 AVL 这边同样的实现,没有暴露出来问题呢。

反正现在修改为多轮循环 test 验证了,验证了几千遍,每次是 1000x1000 次的 push、pop,都是正常的了。

20年8月02日 周日 20:50

做了一个 web 框架:cincau

离职后在家呆了一个月,前面两周在休息,后面两周开始忙起来,想着将手头的数据搭一个可视化的网站出来,可以学习一下 web 前端的库之类的,但是呢,想找一个能 sqlite 和 nginx 的 web framework,找到一个感觉还可以的 Lor,但是呢,不支持 sqlite,感觉不算是一个 minimalist 的 web framework,就想着不如自己搭建一个吧。

可是如果搭建,顺便带上自己的 mnet 是肯定的了,当然 nginx 也是要支持的。于是参考了 lor 的不少东西,但更多的其实是自己摸索的,因为时间也比较充足,是先考虑了架构,才开始动手的。

没有浪费之前买的 goodnote 5,用来构思框架了,画了大概 5 个页面,大的模块上,跟 lor 一样,分为基础库,以及具体的项目代码。基础库在 /usr/local/cincau,用来生成基本的 demo 项目,本身包含了所有 proj 用到的的基础库。对上面的简单业务逻辑,封装 engine 层,不区分为 mnet 或者是 nginx,但实际上这两者一定是要区分开的,除非是静态的文件。

基础库里面,细分为 engine 层、router、MVC 逻辑、database、POST 的解析、HTTP Request 这几个部分,其中 MVC 中 view 的 rendering 用了 leafo 的 etlua,这其实是基于 openresty 的 lapis 的一个模版库。database 不出意外,我只考虑了 sqlite3,就是 minimalist 的 web framework,等有需要,在考虑 mysql、postgresql 之类的,毕竟这两者,没有基于 mnet 的接口,只有基于 nginx 的。

在 goodnotes 5 上面花了 3、4 天这样,其实上面这么多细节,都是在完成整个项目后,回头才细化出来的。在草图出来以后,框架搭建花了大概一周,mnet 是基于 ffi_mnet.lua 以及 hyperparser 的回调,拿到 http 数据结构层。而 nginx 是 content_by_lua_file 来驱动的。

然后还继续花了 3、4 天来完善框架细节,比如 POST 以及 HTTP request 的那一套,以及基于这个框架,写了一个 demo project,支持 ment、nginx,包含静态页面、mock 的 model,以及 post x-www-urlencoded 和 multipart/form-data 这几个部分。

该放地址了:cincau,话说之前也断断续续做过半个 web framework,现在终于点连成了线,水到渠成了。

goodnote 5 画出来的框架图:

20年7月25日 周四 11:22

Curl 的 Lua 绑定,以及 multi 接口

尝试了一下 curl 发送请求的 multi 接口,另外的 easy 接口是一个阻塞的设计,将 easy handle 放到 multi 中后,变成了非阻塞的接口。

大概是下面这样的逻辑,是非阻塞的,目前遇到的遗憾,是 perform 以及 wait 都没法知道具体是哪一个 easy handle 请求结束了。

easy = curl.easy()
easy:setopt(...) -- custom request header, response header, body reader
multi = curl.multi()
multi.add_handle(easy)
repeat
    multi.perform()
untile multi.wait() <= 0

另外还试了一下 curl 中的 event-driven 接口,其实也还是 multi,但是区别于 multi.wait() 基于 底层 select 的设置,这里的 event-driven,依赖比如 libuv、libevent,以及我想集合进去的 mnet 的 事件循环。

创建这种 event-driven 的 curl 程序,还需要需要底层提供一个 timeout 的回调,也许是创建 socket connect 后, 回调具体的 fd,有了这个 fd 才能将其加入到 epoll、kqueue 里面去。

但是我自己验证的结果,感觉程序上跟 github 里面的 example 已经很像了,但一些请求没能成功。

这种方式需要设置好几个回调,在这个基础上设计中间件太复杂了,不管是输入的 header、post data,还是回调拿到 header、 body data 都太复杂了。

另外这种 event-driven 的方式还有一些隐含的条件,比如 timer 是独立于 fd 的,在 fd 前面就创建了,而且 timeout 的单位是 micro seconds,这其实是一个精度很高的 timer 了。

这里我也有疑惑,比如对于 curl 来说,这里的 event-driven 只是借用了 epoll、kqueue 的内核事件机制,我不大理解为何需要由 curl 来 创建 socket,为何不是更底层来管理 socket fd,其实参数交给 curl 来设置都可以,底层只是保证非阻塞就可以了。而 curl 更多的是面向 HTTP 这一层的中间件,raw data <-> HTTP structure data 的转换。

但不管怎么说,curl 这套东西还是太香了,毕竟每个发行版都有,每个发行版安装 dev 或者 command line tool 之后,有了头文件,就可以立马基于 libcurl 这个库进行发开发了。

因为自测 event-driven 没成功,相关信息也比较少,最后是用了上面 multi 接口包裹 easy 接口的非阻塞式设计,用了 Lua-cURLv3 这个库,就请求而言,单独使用了 select。

20年7月25日 周四 11:22

HTML 的 Content-Type: multipart/form-data

研究了一下,Mac 下轮着 Safari、Chrome、FireFox 前后台调通后,这里大概记录一下吧。

前端页面 HTML

在前端 HTML 的写法是,form 里面指定 enctype 为 "multipart/form-data",如下

<form class="cell" action="" method="POST" enctype="multipart/form-data">
    <input type="file" name="myfile" />
    <input type="submit" />
</form>

后端解析

假设在浏览器里面点击 "file" 按钮,选择发送的是一个 PDF 文件,协议上收到是下面这样的,... 是省略:

POST /path HTTP/1.1
...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary5WeF4Lu8GyQKEIE5
Accept-Language: zh-cn

------WebKitFormBoundary5WeF4Lu8GyQKEIE5
Content-Disposition: form-data; name="myfile"; filename="noname.pdf"
Content-Type: application/pdf

%PDF-1.3
...
%%EOF

------WebKitFormBoundary5WeF4Lu8GyQKEIE5--

我自己没有试过多文件,但是从网上资料看,多文件跟单文件不同的是,前面一个 boundary 结束符后,再来一个 boundary 的 Disposition 描述分割,开启下一个数据块的传输。

代码部分就不贴了,感觉有点挫,考虑了多文件分割,以及需要传递给用户文件名、类型等信息,最后是使用了阶段状态的描述,毕竟后端收到的数据是不定长的输入。

--

最后还是整了一下代码,加入了 builder,在 multipart-formdata-lib.

20年7月16日 周四 12:04

LuaRocks with LuaJIT

标准版的 LuaRocks 只有原版的 Lua 可以配套使用,torch 为了让 LuaRocks 能够支持 LuaJIT,修改了一个版本,其实只是修改了编译过程,毕竟 LuaJIT 完整支持 Lua 5.1 的语法。

torch 的版本支持 LuaJIT 的各个版本,但最新的只到 2.1 beta2。早上我将 LuaJIT 的支持版本升级到了 2.1 beta3,地址是 luajit-rocks,然后还发了一个 pull request。但是这个项目有好几年没有更新了,不晓得会不会有 maintainer 会注意到。

torch 的 luajit 其实还有修改 2 个地方,一个是 string 的 hash 方案,一个加入了交互用的 readline。交互部分明显有改善,而 hash 部分不晓得为什么要这么改,应该也还好吧,反正为了 pull request 这里我是不敢动的。hash 用的是 tommy 的方案

在 Linux 中编译了一把,顺利通过,后面再看需要安装吧。在自己的 MacBook Pro 上安装好后,install 了 busted 和 moonscript。不得不说,其实是为了试一下 moonscript,硬是找到了 LuaRocks 的 LuaJIT based 方案。

先用一阵子再说吧。

20年7月15日 周三 22:38

MacOS 下使用 BitBar 在状态栏自定义信息

如果自己有一些信息需要通过点击状态栏图标后获得,或者通过点击状态栏图标启动、关闭的,都可以使用这个工具 BitBar 来操作。

安装好后,只需要定义 plugin 目录,然后在 plugin 目录下加入功能脚本就可以了。脚本其实只关注输出的文本,操作部分其实是通过特殊命令来定义的。

比如例子中的 refresh.sh,精简后是下面这样:

if [[ "$1" = "restart" ]]; then
osascript <<EOD
	tell application "BitBar" to quit
	delay 1
	tell application "BitBar" to activate
EOD
fi

echo "↻"
echo "---"
echo "Refresh Me| terminal=false refresh=true"
echo "Restart Bitbar| bash='$0' param1=restart terminal=false";

会在状态栏显示一个圆圈刷新,点击后下拉菜单会有 "Refresh Me" 以及 "Restart Bitbar"。

可惜的是我虽然弄懂了一点点如何编写这个 Bitbar 的 plugin 命令,但是通过 Bitbar 控制的程序最后没有符合我的预期,:-(

20年7月14日 周二 11:34

GitHub 定义 Repo 语言

之前写的 LWTheme 兼顾 Swift/ObjC 语言,GitHub 改版后,在 repo 里面没有语言百分比的显示了。

搜了一下,可以在 branch 里面定义 .gitattributes 文件,里面这样写

*.swift linguist-language=Swift

自测了一把,网站只会读取 default branch 里面的 .gitattributes,切换其他 branch 没有用。

20年7月9日 周四 15:44

OpenVPN 配置记录

其实考虑过几个比如 ShadowSocks 之类的,但是很不稳定,考虑最后也许还是用 VPN 来得稳妥一些,折腾了半天,终于跑通,先这里随便记录一下吧。

大概分两个部分,一个是服务端的配置,一个是客户端的配置。

客户端我在 Mac 下用的是 TunnelBlick,速度其实一般,还没长时间使用,后续先观察一阵。

20年6月21日 周日 11:39

Text Rank 算法

TextRank 是一种抽取式自动文摘算法,随便在网上搜一下相关,或者在 github 上面搜一下,介绍都一大把的。可是绝大部分的文章都只是堆 python 然后说要怎么做,也许是我的理解能力问题,在有限的几步里面,不晓得这么做的原因,会感觉很头疼。

这里尝试从大的方面还原这个算法,然后给出自己的实现,不过感觉自己的实现有些问题,效果没有想象中那么好,不过也总比没有强吧。

先开始吧。

TextRank 是一种抽取式的文章摘要算法,其最后的输出内容,都来源于文档本身的句子。这些句子会被算法打分排序,依靠的其实是 PageRank 算法。

PageRank

PageRank 算法原来只是为了描述网页间的重要程度,重要程度的计算,来源于他们间的超链接关系。简单的说,被链接得越多的页面,越重要,跟重要页面有链接的页面,比其他的页面更重要。准确分值的计算,是根据下面的公式来的:

img

其中,PR(Vi) 表示网页 Vi 的分值,In(Vi) 表示 PR(Vi) 包含的网页集合,Out(Vj) 表示 PR(Vi) 链接出去的网页集合,d 是链接传递系数或者说网页跳转的可能性,常用的值是 0.85。

可以把这个公式的计算基础描述成一张图,网页就是节点 PR(Vi),边就是跟 PR(Vi) 有关系的 PR(Vj) 链接集合。

初始化的时候,各个 PR(Vi) 的分值是不确定的,只有边的链接描述,因此需要迭代计算,比如都给各个 PR(Vi) 初始化为 1,通过公式多轮不断的迭代计算,当各个 PR(Vi) 分值稳定的时候,就是各个网页的 rank 分值。

Sentence simularity

如果把上面的 PR(Vi) 当成句子,我们就可以套用 PageRank 公式计算出句子的 rank 值,并提取重要程度高的句子作为文章摘要描述。

但是如何确定不同的句子有边的联系呢,算法提供了计算句子间相似程度这个标准,使用下面的公式:

img

其中 wk 是句子 Si 及 Sj 都有的公共词组,分母是词组的数量。当然,也可以用句子的余弦相似度来计算,但是词组本身就得包含向量值了,这些词组,可以称为 word vector。

Word Vector

词组在中文里涉及到分词,有不同的工具可以用来分词表来构建语料的词向量表,比如有的用 word2vec,有的用 GloVe。话说,除了上面的模型、算法之外,语料可是相当重要的基础资源呀。

这里当然还涉及到使用哪些范畴的语料来构建这个 word vector,这里不详细叙述了,因为网上相关资源就更多了。

最后

TextRank 算法应该就是上面这些了吧,虽然为了搞清楚查了很久,最后推一把自己堆的 text_rank,基于 LuaJIT 和 THULAC,随便跑起来,内存就占用了 1 个 G,搞不起搞不起。

20年6月6日 周六 19:29

Lua 的优点

接着上一篇 Lua 的缺点,作为平衡,这里补充一下。

作为一个普普通通,用过 ObjC、C/C++、Java、Swift、Emacs Lisp 的开发者,也是可以把 Lua 的一些好用的方面吹一下的。由于没用过 Python、Ruby、JavaScript,下面的观点未必就是 Lua 对比其他脚本语言鲜明的优点。

其实呢,我是将 Lua 跟上面所列的编译型、静态类型的语言做对比的,:)

Lua 是简单的

Lua 是命令式的语言,感觉以 C/C++、Java 起步的同学,学习 Lua 完全没有难度,Lua 语言没有几个关键字,控制结构甚至没有 continue。

而容器结构,只有一个 table,融合了 Array 以及 HashMap,当然,table 的作用不止于此,后面有更进一步的描述。

Lua 很小易于移植

Lua 语言只有核心部分,VM 代码只有几万行,是古老的 C89 语法,而且运行期的 VM 内存占用也不多,大概几百 kb 这样。

这使得 Lua 的编译安装是毫无难度的,支持 C89 的 C 编译器、硬件遍地都是。较少的资源消耗,意味着,在如今的操作系统上,可以大大方方的将几十、几百个 Lua VM 跑起来。

Lua 的核心还可以配置,将 Number 配置为 32bit 的 Integer,在低端没有浮点数的平台上,也能使用。好早之前,我就在 Rockchip 的的平台上应用上了 Lua,没有浮点数,Number 设置为了 32bit 的 Integer,用得好好的。

所以,不用担心写的纯 Lua 代码是不是在某些平台跑不起来,不存在的。

Lua 很稳定很快

Lua 生于 1993 年 7 月 28 日,语言核心早就稳定下来了,工业界早已采用多年。

LuaJIT 很快很快,可以看一下 LuaJIT-2.0 对比 Lua-5.1.5 版本的效率对比 Performance: x86/x64。如今 Redis、OpenResty 标配 LuaJIT 作为内建的脚本支持。

Lua 是动态类型的

写应用程序的同学都了解,声明一个变量再去声明类型是有多重复,所以最新的 C++、Swift 对于类型都是可以自动推断的了。

我是觉得对于模块化设计来说,类型只在模块输入、输出的时候才有作用,比如对于输入的数据,我要知道变量的大小和范围,这也是模块对于这个输入参数声明的支持范围。

但如果真的需要程序关心内存布局的时候,每一个变量需要多大的内存空间,才值得去斟酌,而类型,其实就是内存空间占用的映射。模块内部,既然都是可信的环境,用足够大的内存区域去持有这个变量就行了,这个不影响,反而少了类似 C 这样因为类型转换而截断,导致期望的判断没有成功,引起模块内部错误,不得不抛出异常或者出错,其实毫无必要。

从另外一方面说,如果有了静态类型,可以控制任何一段程序在变量传递、比较时候的范围,但是当我们需要更高程度的抽象的时候,就又涉及到了如何抽象类型,这又涉及到了模版、范型。感觉又复杂了,也许这是所有静态类型的语言,都绕不过去的一个弯吧。

Lua 支持多种编程模式

Lua 中可以加载一段 String、一个文件并运行,简单的语法,使得数据描述编程、数据驱动编程是完全可行的。比如我在 Lua 脚本中加载自己,在运行前修改其流程,再运行,是完全可行的。

Lua 的函数是一等公民,可以实现 lambada 算子,函数式编程妥妥的没问题。

Lua 可以实现面向对象编程,上面有提到 table,当在 Lua 里面实现面向对象的时候,对象实例其实就是一个 table,对象的属性只不过是 table 里面的一个 key,函数跟 Number、String 一样可以作为 key 的 value。

当这样调用对象方法时,table:func(value),func 默认第一个参数会传递 self,就是对象自己,一个 table,第二个参数才是 value。

理解 Lua 的面向对象编程,需要了解 Lua 内建的细节了,比如 table 的 __index key 是指找值的时候应该去哪里找,__newindex 则是创建新值的时候应该怎么做。多说一句,如果想 table 是 readonly 的,修改 table 的 __newindex 就对了。

下面是 Lua 的面向对象编程类定义的一个例子,a = Object.newObject(1), b = Object.newObject(2), a:isEqual(b) 将返回 false。

local Object = {
    value = 0
}
Object.__index = Object

function Object.newObject(value)
    local obj = setmetatable({}, Object)
    obj.value = value
    return obj
end

function Object:isEqual(obj)
    return self.value == obj.value
end

function Object:isGreater(obj)
    return self.value > obj.value
end

return Object

更进一步,Lua 的面向对象可以定制 __index 来实现单继承,甚至多继承,实例变量、类变量等,面向对象用这个库就对了 kikito/middleclass

Lua 元表

话说 table 在 Lua 中占据了中心位置,既是 Array 又是 HashMap,面向对象的时候又是对象实例本身,定义的时候用到了,实例运行期间也用到了。

table 有这么多的身份,定义不同身份时候的行为,成了关键。在 Lua 里面,table 的 key 怎么寻找,怎么设置新值,两个 table 之间怎么相加、相减等等,是通过定义 table 的 metatable (元表)来实现的。

面向对象的例子,就用了 Lua 的内建函数 setmetatable,定义了 Object.__index 的行为,限制 key 值应该在哪里寻找。

这里有一篇 Lua 元表 的教程。

多说两句吧,结合面向对象 Object 的例子,我们定义一下两个 Object 相加的行为:

local Object = {}
Object.__index = Object
Object.__add = function(oobj, nobj)
    oobj.value = oobj.value + nobj.value
    return oobj
end

这样,如果 a = Object.newObject(1), b = Object.newObject(2), a = a + b 后,a.value 等于 3。

话说连 java 都没能实现的运算符重载,Lua 通过 table 的 metatable 实现了。

Lua 跟 C 的交互

官方 Lua 跟 C 的交互,需要用到 Lua 的 C API 定义 Lua 可以使用的 C 库,在这一层 wrapper 中再调用其他编译、链接进来的其他库,可以看下这篇 Lua C API 研究 —— 基础篇,英文的 Exposing C functions to Lua

如果用的是 LuaJIT,对于已有 C 库的使用,就方便多了,用到了内建的 ffi 库来声明二进制库中声明的函数接口、用到的结构等,官方教程及例子 FFI Tutorial,这里不单独列举了。

--

Lua 作为一种简单、稳定、快速的命令式语言,提供高度的抽象,又可以很方便地使用已有的 C 库资源,对于我这种从 C/C++ 起步的程序员来说,相当友好。

酒醒了,吹一波不为过,:)