免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: __BlueGuy__
打印 上一主题 下一主题

[C] Lua 造成的代码冗余太严重了, 这个现状怎么改善? [复制链接]

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
91 [报告]
发表于 2012-11-18 21:22 |只看该作者
回复 85# starwing83

另外,前面说掉了。别转emacs。。。 我这还准备转出呢。。。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
92 [报告]
发表于 2012-11-18 21:32 |只看该作者
回复 82# fergon

这个我也不知道。。。
按yesod的官方说法是。。。 其实这个库不太需要haskell的知识。。。  但这种说法。。。 我觉得太夸张了。。。

比如router。 如果没有template haskell相关的知识, 完全不知道router的DSL里的名字是和具体的函数怎么对应上的。。。 总之magic happends here。
yesod有一个新的不依赖template haskell的分支, 不知道发布了没。
为什么又要弄一个不依赖template haskell的分支? 难道不是因为编译错误信息太魔幻了么。。。

再比如iteratee, 我知道它想解决的问题, 但不知道它是怎么解决的。 动不动就一堆paper。。。 估计还是只能有时间了看代码。。。


总之,我觉得,用yesod也行吧。。。  对haskell这种奇葩,没办法先练到不踩雷了然后再去实践。。。 也只能一边踩雷一边学了。。。

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
93 [报告]
发表于 2012-11-18 21:34 |只看该作者
回复 88# OwnWaterloo


    你还是没科普你所说的“异常”。

这个先放一边,反正你得在这个帖子里面说明白了,啥时候写都行。

上面那个贴就当是纯吐槽了……回你这个贴。

这里我是提出了一个“虚拟”的接口你是看不出来么……

说一下具体的。

Lua有loader机制。所有的loader被放到一个表里面去。

require的时候(注意这是运行时的),会逐一用你传给require的那个名字去调用loader,loader判断自己找不找得到这个模块,如果找到了,就返回一个一调用就能载入模块的函数(称为loader),找不到,就返回一个字符串说明为什么找不到。

注意这里有个术语冲突,找loader的函数自己也叫loader,所以在5.2名字被改,找loader的函数被称为searcher,而那个loaders的表被改为package.searchers。

现在假设我要载入一个相对模块,比如我们依照Python的习惯。impor .foo载入本文件所在目录下的foo.lua,而..foo载入本文件的父目录下的foo.lua。

首先得取得调用require所在地的那个文件的名字。

这就有问题了。首先,这个require调用所在地未必有名字,可能是:

1. 该函数未必直接在文件里。
2. 调试信息被strip。
3. 该函数未必是在Lua中被调用的。

这就很麻烦了。第二点是完全没辙。第一点倒是稍微有点戏。上面说的就是这个第一点。首先searcher必须协商一个能对searcher和Lua的debug自己能识别的规范,让Lua理解这个文件到底是以什么状态存在的(这一点很重要,否则会导致debug相关的其他函数——甚至包括error这样的函数——不兼容)。而debug,总所周知,规范只有三条……前缀@,表示文件,前缀=,表示显示名,其他情况,表示这个名字本身就是程序本身………………根本不留任何余地的说。searcher在载入的时候倒是可以给出一个有“个人风格”的命名方式,下次找到的时候可以按规则处理,但是得冒——同样的——甚至包括error这样的函数不兼容的风险。

第三点直接就是没戏。

其实和前面那个动态重载是一样的。没办法完全解决这个问题。只能保证在“正常情况”下没问题。但是不保证完全没问题。

另外,注意到我给出的那个代码里面debug.getinfo(3)了没?这个3是个硬编码的,不具备可移植性。意思是也许到5.3就变了,得是4了……

总的来说,就是,这事儿做起来很麻烦——但是这种麻烦不是模块系统造成的,是Lua其他系统和模块系统的交互造成的,是Lua在设计上,对此提供的机制不足造成的。



论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
94 [报告]
发表于 2012-11-18 21:40 |只看该作者
回复 91# OwnWaterloo


    也说掉一点,首先请看89楼。其次,其他语言能做那种“载入xxx文件的时候,看到require,就顺便咋地咋地了”的原因,是因为require是语法,是编译期的……Lua在模块依赖分析上遇到的各种问题就和这个有关系。require是个普通函数,那么你完全可以做这种事情:

foo = require
foo "abc"

那除了直接执行这个脚本,否则根本不可能有人知道你载入了一个模块叫做foo。这简直就是跟Python里面True = False是完全一样的奇葩。。。叫这丫的True和False不是关键字,面向对象面傻了吧………………


不过,我们可以假设没有哪个傻逼会这么用,那么做依赖分析就方便多了……不过很遗憾地,对Lua本身这还是不行——因为这货在Lua的执行中,依然是运行时的。

所以嘛……

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
95 [报告]
发表于 2012-11-18 22:32 |只看该作者
starwing83 发表于 2012-11-18 21:18
果然是这个………………这个的确叫reload,不过似乎叫“自动重载(auto reload)”我说的云风做的就是这个(不是Pluto神马的,你的dump把我误导了),最开始我说的就是这个。只是你说repl的结果最终可以persistent到文件这个说法把我误导了。

所以其实与dump出源代码没有关系。 dump是你再提好吧, 我只说过persistent。
源代码既是发布用的东西, 也是复现一个process的东西。 dump只适合后者。 前者需要dump出的东西是人可读的代码。

有很自动的工具,不过目前我还没时间去弄。。。  管理依赖太烦了。。。 所以。。。 只在工作室用lein。。。 尝试。。。
觉得可以了再看怎么自己配。。。



starwing83 发表于 2012-11-18 21:18
是的,我很想让Lua+Vim可以做到这样,需要做到这样的话,需要这样的工具:

1. Vim能向程序输出一个信号,表示某文件已改变。
2. Lua程序能收到这么一个(跨进程的)信号,得知文件已改变。
3. Lua能在不中断所有执行一致性的前提下,重新载入已改变的文件。

第一点不太难,不过得约定好输出的信号是啥。Vim自己内部本身是可以执行Lua程序的,所以将Vim本身作为host也不是不可以。但是Vim本身是个编辑器,编辑器有自己的事件循环,如果你的程序也有事件循环的话(基本上需要做自动重载的都有自己的事件循环——不然你也没办法重载啊),那基本上就是完蛋了。别的选择就不是很多了,socket,进程间通讯,消息队列,反正事儿不难,但也不太简单。

第一点解决,第二点就解决了。Vim保存文件并通知改变,Luahost的事件循环监听这个信号,并应用改变。


你这太自动了。。。 如果真要做好这事倒可以不怎么在乎代价。
我最终的构想。。。 给你说过的吧。。。 不能任意编辑, 只能按正确的方式展开。
一个特殊的编译器维护一堆definition。 用户直接要求它增加/修改/删除这些definition。
这个过程中编译器可以根据现有的状态补全, 宏展开, 语意diff/apply。。。
编辑器的作用就是一个可视化的界面, 可以通过它看到这堆definition的样子, 以及通过它来要求编译器进行增加/修改/删除。。。

目前来说的话,可以半自动一些。。。 手动告知有哪些更新(否则基于文件改动的话,改一个函数重新加载一个文件有点。。。)
低代价 —— gnu emacs自带了好几种进程通信方式,但就是不给C module扩展, 不过给了我也不敢写。 高收效 —— 可以节省很多启动时间。


starwing83 发表于 2012-11-18 21:18
我第一次回的帖子实际上在说第三点。我的观点是,即使你用dofile,依然不能完全解决自动重载中遇到的问题。问题是这样的:

local string_gsub = string.gsub

for line in io.lines() do
string_gsub(line, ....)
end

这样的代码,函数是缓存的……

这样基本上你就完蛋了。

意思是 string.gsub 修改了, 那个for循环依然用的旧的?

这样的问题在用repl的开发方式里都会遇到。。。
比如:
(run-jetty app {})
(run-jetty #'app {}) ; 这样写,app的定义改变后才能立即生效。
目前不想管这事, 不就一次查询么。。。  开发方便才是王道。。。


dofile不能完全解决,也没想过dofile就能解决。
dofile的场景是这样。上面的例子不好,要添加一个由私有库的外部库。 假设是ring.middleware.params, 再假设会有一个内部用的base64实现。

如果params里用load加载base64, 那params就是一个整体。
首次加载它会同时加载params和base64, 重新加载它还是会重新加载params和base64。 就像是写在一个文件里一样。

如果有好的依赖分析,倒是不需要这样。但如果没有,dofile可以顶着用用。


starwing83 发表于 2012-11-18 21:18
那么,能支持动态重载的模块,你必须保证,它的使用者,完全不会因为各种原因缓存模块里面的函数。因为Lua函数本身的灵活性,所以数据是无所谓的。

但是,如果数据格式上不兼容,为了不报错,恐怕数据重载也是必须的——这一点纯函数式的确是优势,但是恐怕也不明显。

dll的模块绝对不能用这种方式重载,一来是dll本身在注册表里面会留一个存根(用于退出的时候垃圾回收),这个存根得去掉,但是Lua没留办法,这倒不是说退出的时候会崩溃,这是说你根本无法改变这个dll——因为它会被Windows锁住。具体的方案得仔细研究Lua源代码才能得出,反正这不是很容易的事情。退一万步说,就算dll可以被重载,万一其userdat的格式不一样,那崩溃几乎是唯一结果了。

但是,就算只有这一点,保证也不是容易的:你如何保证,你的模块所有的使用者,都不会缓存这个模块呢?

最简单的办法,可能就是仅动态重载某些特定的模块了——我们以前的活动模块,导出函数只有OnDraw, OnInput等等几个,而且这些函数绝对不会被缓存(最初由同学设计的驱动循环是将这些函数逐一缓存的………………我@#¥%#%……@#……后来改过来了……)

通用的动态重载,在Lua里面是不可能的(或者至少是要靠运气的)。

不需要很通用。 那个for循环,lua分析不出它依赖string.gsub,人肉还分析不出来么? 至于dll根本就没想过。
dofile也不是为了这个就要支持相对路径; 而是说支持相对路径的话, 没有依赖分析也都可以勉强凑合着用了。

比如仓库里是load, app, 本地文件是require, #'app, 然后这两行提交永远不进入仓库。。。 只在开发时用。。。
是谁让我找work around的。。。  如果要先把clojure的理想开发环境弄出来再干活, 那直到现在都干不了活。。。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
96 [报告]
发表于 2012-11-18 22:40 |只看该作者
回复 94# starwing83

是啊,没有假想require是语法啊。 只在py,haskell里是,而且haskell估计没办法不是, py还有其他办法。 但el, clojure都不是啊。
不是"载入xxx文件的时候看到require"。

而是有一个当前载入文件的栈,负责载入的那个函数而非语法会维护这个栈,并且在参数是相对路径时会查看这个栈。


另外,跑题说静态分析。
foo = require
foo "abc"
都比 require(f(arg)) 或者 f(arg)("module")要容易。。。
不过相对路径载入是运行时的事。。。

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
97 [报告]
发表于 2012-11-18 22:45 |只看该作者
回复 95# OwnWaterloo


    继续提醒你说异常的事儿………………

既然是这个目标,那么就简单了。

另外,这个依赖分析实际上是反的,是说“载入了这个模块的模块都必须重新载入”,是吧?这样就可以解决本地缓存的问题了,不过速度嘛……

如果不是这样的话,其实是不需要依赖分析的:因为require是运行时的事情,require旧的就旧的呗,你重新载入params,用的是旧的base64,反正base64没被改,怕毛…………

不是说一次查询就一次查询……是说万一有人缓存(比如说无意中的话缓存神马的),那就完蛋了。

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
98 [报告]
发表于 2012-11-18 22:47 |只看该作者
回复 96# OwnWaterloo


    你这种不行。很可能我在这个文件调用其他文件的函数,在函数里面require(这很正常)。照你这个设计就全乱了。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
99 [报告]
发表于 2012-11-19 00:07 |只看该作者
本帖最后由 OwnWaterloo 于 2012-11-19 00:09 编辑
starwing83 发表于 2012-11-18 21:34
你还是没科普你所说的“异常”。

这个先放一边,反正你得在这个帖子里面说明白了,啥时候写都行。


我就是还没来得及写啊。。。 你这到底是要我这就写还是等会写。。。   就现在写吧。。。

C++,Java(Clojure),C#,Python,Elisp,CL,Scheme,它们 —— 或者只说Python之前的那些,因为后面那些的non local jump各有各的不同,也不自称为异常 —— 的异常是可以一路走到底的。
发现错误的点抛异常。 想处理错误的点写try/catch。 不想处理错误的点写try/finally或者析构或者with。 不需要进行exception <-> status code的转换。

C++得单独算, 如果不考虑编译器兼容, 也是可以一路走到底。
C++如果考虑编译器兼容, 就要在编译边界进行 exception <-> status code 的转换。 边界之中, 异常也是可以走到底。
而lua。。。 转换粒度比C++还小是闹哪样。。。



这方面Lua只能和C归到一类, 只有non local jump,没有配套的non local jump发生或不发生时执行相应动作的机制。

错误报告用longjmp。 无论想不想处理错误, 都得写setjmp <- 推论就是这相比status code没有优势,反而劣势一大堆,于是就不会被普遍采用。

不同点是Lua有first class function(closure)而C没有。 于是就可以将一些重复的代码块写为函数protect/newtry, 然后复用,而不是次次都setjmp/pcall, longjmp/error。
但lua不是lazy的,也就是说传递给这些函数的不能是代码, 而必须将代码包裹在一个function里, 延缓它的求值。而且, lua的function literal又那么长。。。

总之,用error的优势在哪里?
到底有多少人会去主动用error的? 不是因为底层的库用了non local jump所以他们也被迫用? 而是他们自己觉得应该用error? 这样的库被lua社区接受的又有多少?
多少人是因为lua的一些内建函数用error,所以他们不得不pcall? 如果lua内建函数都改为status code,他们会就开心得不得了?

如果只讨论潜力,现在听得到的语言没几个不是图灵完备的,潜力都一样。
哪又是什么导致这些语言有一样的潜力, 但就是各有不同? 因为在不同语言里做不同相同的事代价不同。
如果某种编程方式代价过高 —— 比如java8之前连function literal都没有 —— 怎么玩高阶函数相关的东西? C++没有lambda之前, algorithm也基本是个废物。
而那种模棱两可的, 说高不高说低不低的, 就不会被广泛应用。 比如lua的异常处理, scheme的先构建编译器再编程。 你能给我数据说明这两者的比例真的不是一些geek在玩, 而是真的有不少的人在这么用?
我在C++里尽量避免用异常也是这个原因, exception <- > status code转换太烦了。 这里多出的代码用exception不一定省得下来, 那为什么还要用?

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
100 [报告]
发表于 2012-11-19 00:36 |只看该作者
starwing83 发表于 2012-11-18 22:45
另外,这个依赖分析实际上是反的,是说“载入了这个模块的模块都必须重新载入”,是吧?这样就可以解决本地缓存的问题了,不过速度嘛……

这样做的代价呢。。。
A载入了哪些是可以从A的代码里顺藤摸瓜找出来的。
A被哪些载入了。。。 这个。。。
google那个什么忍者项目到底是做啥的?


starwing83 发表于 2012-11-18 22:45
如果不是这样的话,其实是不需要依赖分析的:因为require是运行时的事情,require旧的就旧的呗,你重新载入params,用的是旧的base64,反正base64没被改,怕毛…………

就是在说如果params和base64都有改变。。。 而且没有健壮的依赖分析的情况下。。。

如果params用load,会有一些优势。。。
这种组织方式, ring.middleware.params 是一个加载单元, 哪怕它其实是由多个文件(params.clj与base64.clj)构成。
不能加载单独的文件, 只能是ring.middleware.params。 而且无论是用require还是load还是require时告之要reload, 只要确实加载了就会加载两者。

当然这只是work around, 最好还是有一个分析依赖的东东。
所以最开始我列这一点时(那个帖的第4点), 写了后又删了。


如果lua支持相对路径, 那这种work around虽然脏, 但只脏自己。 只要不提交, 别人是看不见
- require
+ dofile
这个改动的。


starwing83 发表于 2012-11-18 22:45
不是说一次查询就一次查询……是说万一有人缓存(比如说无意中的话缓存神马的),那就完蛋了。

缓存指的是 local f = lib.f 然后使用f吧?
就使用经验来看, 如果要在repl里从新定义lib.f, 要么就直接使用lib.f要么就人肉分析依赖。。。 凑合着可以用。。。
如果能有机器自动正确地分析当然更好。。。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP