huaxiazhihuo

 

lisp的括号

       lisp(当然也包括scheme)的元编程(也即是宏)威力非常强悍,相比之下,c++的元编程(template+预处理)简直就是弱爆了,被人家甩几条街都不止。 当然,template的类型推导很厉害,也能生成很多签名类似的class和function,比其他语言的泛型强多了,但是,template再厉害,也不能生成名字相似的function还有变量。 预处理可以生成名字相似的变量和函数。但是,预处理的图灵完备是没有类型这个概念,只有字符串,整数那个东西还要靠字符串的并接来实现。所以,预处理没法得到template里面的类型信息。新版本的c++中有了decltype之后,宏可以通过某种方式以统一的形式来利用类型信息。但是,在代码生成方面,预处理还是很弱智,主要的问题在于宏对于自己要生成的代码结构很难构建语法树,也不能利用编译阶段的功能,比如调用编译阶段的函数。 想说的是,很难以在代码中只用宏来写一个稍微复杂的程序,即使做得到,也要吐好几口老血,还煞难调试。
       lisp就不一样了,宏和语言融为一体,以至于代码即是数据。只要你愿意,完全可以在lisp中只用宏写代码,只要愿意,分钟钟可以用lisp写一个dsl,比如loop就是一个专门处理循环的dsl。甚至,用lisp宏还可以做静态类型推导的事情,也非难事。因此,用lisp宏搞基于对象 (adt)也都有可能,从而优雅的使用.操作符。比如(+ obj1.item obj2.item) (obj.fun 2 "hello")。你说,lisp宏连.操作符都可以做到,就问你怕不怕。
       但是,宏再厉害,也不能随意地搞底层操作内存。恰好与c++相反,c++搞底层随意操作内存太容易了,但是元编程的能力就远远不如lisp了。
       emacs是最好玩的ide,注意不是最强大,猿猴随时可以写代码增加改变emacs的功能,马上见效,不需要任何配置,不需要重启。因此,elisp也是最好玩的语言了,因为最好玩的ide的脚本语言就是它了,呵呵,主要原因还是elisp是lisp的方言,可以承担lisp的很多构思,当然,完全继承是不行的,不过,已经足够做很多很多的事情了。
       不过,本期的话题是lisp的括号,为什么lisp会有那么多的括号,铺天盖地,很容易,就一堆一堆的括号扰人耳目,以至于lisp代码不好手写,只能忽视括号,依靠缩进。括号表示嵌套,相必之下,c系语言的嵌套就没那么恐怖了。一个程序,顶破天,最深层都不会超过十层,连同名字空间,类声明,函数,再到内部的for,if,大括号,中括号,小括号。
       中缀表达式,这个众所周知了,试比较,1+2-3*4/obj.width,没有任何括号,依靠运算符优先级表示层次关系。并且,猿猴也习惯并本能的解析中缀表达式了,因此,代码看起来一目了然。lisp就很可怜了, (- (+ 1 2) (/ (* 3 4) (obj-width obj))),这里面多了多少括号,在转换成这行简单算式的时候,还是在emacs下面写出来的。关键是,虽然前缀表达式没有任何运算级别上的歧义,但是,人眼还是比较习惯中缀表达式了。君不见haskell的括号更少了,其对中缀表达式和符号的运用更深入。关键是,中缀表达式很容易手写啊。易写,自然也表示易读。C#的linq的深受欢迎也因为其好读,无须在大脑里面建立什么堆栈,linq表达式就是上一个处理的结果通过.操作符传递到下一个运算中,非常顺畅,不必返回前面去看看当前的操作数的运算是什么,因为运算符就在眼前了。中缀表达式.操作符,更是灭掉括号的大杀器,比如,obj.child1.child2.value,这里用lisp来搞,4个括号避免不了的。
       试试将java万物皆是对象推向极致,然后没有中缀表达式,1.plus(2).minus(3.mult(4).obj.width)),比lisp要好一些,但也有很多括号了,并且,在minus这里,其括号嵌套也只是减少了一层而已。当表达式复杂起来的时候,这种缺点也要相应的放大。
       变量的就地定义,好像c系的变量要用到的时候才定义这种语法很稀疏平常,没什么了不起的。但是,到了lisp下面时,就知道这是多么贴近人心的便利啊。每次用到新变量,都要引入let表达式,又或许跑到前面的let语句中写变量,要么就打断当前的代码编写,要么就引入新的一层嵌套关系。一个状态复杂的函数,很容易就出现好多个let语法块。而c系的变量就地定义,显得那么淡定。
       return,continue,break等语句就可以把后面的语句拉起来一个层次,假设没有这些关键字,要用if else语句,那么,这些return,continue,break后面的语句都表示要被包含在一个else的大括号中。
       lisp里面的特有语句,with-*等宏,都要求嵌套。几个with-*宏串起来,几个括号嵌套关系就跑不了啦。而c++通过析构函数就多么地让人爱不释手了,java也可以别扭的用finally来应付了。
       控制结构的并行。像是if,for,while或者是class还有函数定义等语句,其后面的代码块是并列在关键字的后面,这样就少了一层嵌套。不过这个作用并没有那么巨大。主要还是前面4点。
       这样,就可以模拟其他语言的特性来灭掉lisp的括号。当然是要到宏了,loop就是一种尝试。但是,下面将走得更远。其实,就是设计一套新的语法了。
       假设这个宏的名字是$block,那么后面的文章就可以这样做。
       1 加入一个$操作符。$表示后面的代码都被收入进去。比如,1+2-3*4/4,就可以写成($block (-) $ (+ 1 2) (/) $ (* 3 4) 4)。于是,with-*等宏的嵌套就可以用$来代替了。虽然,$的作用好像有些欠缺,功能不完备,但是,只要考虑到括号都是在最外层体现的,那么,$就显得很有作用了。
       2 加入let的操作符,表示就地引入变量,其实也即是将变量名字加入上层的(let)的变量列表中,然后在这里插入一条(setq var vlaue)的语句。
       3 加入if,elif,else,for,switch等语句,于是后面的代码块就与之平行了,并且准备一个(let)的语句,用于给with语句添加变量。可以借鉴loop宏的方式
       4 return,break,continue等相应的实现。
       5 支持.操作符,所有关于.的操作,都转换成相应的函数操作,好像以前的cfront在对于成员函数的支持那样子。这里就要有静态类型推导了,可以通过with语句中加入变量的类型说明,给函数添加返回类型的标签。有了这些信息,就可以找到obj相应的成员函数的名字,就可以生成对应的函数调用的form了,这个做起来有点难度。
       ......
       以上,除了第5点,其他都可以借鉴loop的代码来实现。$block里面的代码,便于手写,括号也没有那么面目可憎了。

posted on 2016-05-20 11:17 华夏之火 阅读(2904) 评论(0)  编辑 收藏 引用 所属分类: emacs elisp


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


导航

统计

常用链接

留言簿(6)

随笔分类

随笔档案

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜