http://www.cnblogs.com/Leap-abead/articles/762180.html
非常佩服, 作者的耐心可以写出这么长的文章, 虽然有些太过于罗嗦
也非常佩服, 作者的想象力和对技术本质的深刻理解, 可以将晦涩的Lisp语法和XML划上等号.
重新审视XML
XML是一种标准化语法, 它以适合人阅读的格式来表达任意的层次化数据 (hirearchical data), 其实就是树型结构.
任何可以用树来表示的数据, 同样可以用XML来表示, 反之亦然
源代码在解析之后也是用树结构来存放的, 任何编译程序都会把源代码解析成一棵抽象语法树, 这样的表示法很恰当, 因为源代码就是层次结构的:
函数包含参数和代码块, 代码快包含表达式和语句, 语句包含变量和运算符等等
所以结论就是任意的源码和XML是应该可以互相转换的. 我们可以把XML作为源代码的通用存储方式, 其实我们能够产生一整套使用统一语法的程序语言, 也能写出转换器, 把已有代码转换成XML格式。如果真的采纳
这种办法, 各种语言的编译器就用不着自己写语法解析了, 它们可以直接用XML的语法解析来直接生成抽象语法树。
<define-function return-type="int" name="add"> <arguments> <argument type="int">arg1</argument> <argument type="int">arg2</argument> </arguments> <body> <return> <add value1="arg1" value2="arg2" /> </return> </body> </define>
int add(int arg1, int arg2) { return arg1+arg2; }
而且这个例子也很好的诠释了过去被认为很难解的概念, 并且非常直观非常简单的显现出来。代码也是数据, 并且从来都是如此。
重新审视Ant
Ant是怎样工作的?原理非常简单。Ant把包含有构造命令的XML文件,交给一个Java程序来解析每一个元素,实际情况比我说的还要简单得多。
一个简单的XML指令会导致具有相同名字的Java类装入,并执行其代码。
<copy todir="../new/dir"> <fileset dir="src_dir" /> </copy>
为什么使用XML而不是Java代码来描述构造命令, 到底有什么好处?
为什么不写一组Java类, 提供api来满足基本任务(拷贝目录, 编译等等), 然后在Java里直接调用这些代码? 这样做仍然可以保证移植性, 扩展性也是毫无疑问的。
在语义的可构造性方面, XML的弹性是Java望尘莫及的。
说白了, 就是XML可以随便改动schema, 你想加个tag, 比如copy, 很简单. 而对于Java, 你如果想加个关键词, 就很困难, 因为Java本身的协议和schema是定死的.
所以如果用java来写这个简单的copy就不会那么好看, 因为你不能创造一个copy命令, 如下
copy("../new/dir"); { fileset("src_dir"); }
CopyTask copy = new CopyTask(); Fileset fileset = new Fileset(); fileset.setDir("src_dir"); copy.setToDir("../new/dir"); copy.setFileset(fileset); copy.excute();
对于复杂的算符来说, 这样做的好处显而易见。比如, 用特定的算符来做检出源码, 编译文件, 单元测试, 发送邮件等任务, 想想看有多么美妙。
对于特定的题目, 比如说构造软件项目, 这些算符的使用可以大幅减低少代码的数量。增加代码的清晰程度和可重用性。
解释性的XML可以很容易的达到这个目标。XML是存储层次化数据的简单数据文件, 而在Java中, 由于层次结构是定死的(你很快就会看到, Lisp的情况与此截然不同), 我们就没法达到上述目标。也许这正是Ant的成功之处呢。
Ant需要负责解析XML, 并且将XML tag和实现好的Java function进行匹配, 虽然也可以实现, 但是略显麻烦
离Lisp越来越近
前面说了Java本身不可以扩展, 而不断的通过Java实现来扩展Ant XML tag, 又比较的麻烦
那么是否可以通过Ant XML tag本身来实现tag扩展了? 我们先实现一堆核心Ant-XML的tag, 然后可以基于这些tag, 扩展出其他各种新的tag, 当然这样是可以的
这个想法本身很酷, 但是实用性不高
原因在于XML很烦琐。对于数据来说, 这个问题还不太大, 但如果代码很烦琐的话, 光是打字上的麻烦就足以抵消它的好处。
为了解决这个问题, 我们应当简化写法。须知, XML仅仅是一种表达层次化数据的方式。
我们并不是一定要使用尖括号才能得到树的序列化结果。我们完全可以采用其他的格式。其中的一种(刚好就是Lisp所采用的)格式, 叫做s表达式。
上面的代码译成s表达式是这样的, 确实是大大的简化了
(copy (todir "../new/dir") (fileset (dir "src_dir")))
s表达式打起字来, 也省事得多。第一次看s表达式(Lisp)时, 括号很烦人是吧? 现在我们明白了背后的道理, 一下子就变得容易多了。至少, 比XML要好的多。用s表达式写代码, 不单是实用, 而且也很让人愉快。
s表达式其实就是一种嵌套list, 如下
(copy (todir "../new/dir") (fileset (dir "src_dir")))
希望你不会感到迷惑, 因为嵌套List和树实际上是一码事。Lisp的字面意思就是表处理(list processing), 其实也可以称为树处理, 这和处理XML节点没有什么不同。
结论就是Lisp是可以象XML一样, 简单的进行schema扩展, 进行元编程的, 方便于DSL的开放.
同时Lisp象Java一样, 本身就是可执行code, 所以不需要象Ant一样, 先解析XML, 然后用java执行, Lisp的元编程可以直接被执行
而且Lisp采用list(s表达式), 虽然和XML的表达本质上一致, 但觉简洁许多, 更适合程序员coding
你好, Lisp
到此刻为止, 我们所知的关于Lisp的指示可以总结为一句话: Lisp是一个可执行的语法更优美的XML
什么是List(表)? 你也许已经听过好多相关的说法。List, 一言以蔽之, 就是把类似XML那样的数据块, 用s表达式来表示。List用一对括号括住, List中元素以空格分隔, List可以嵌套。
当Lisp系统遇到这样的表时, 它所做的, 和Ant处理XML数据所做的, 非常相似, 那就是试图执行它们。其实, Lisp源码就是特定的一种表, 好比Ant源码是一种特定的XML一样。
Lisp执行表的顺序是这样的, 表的第一个元素当作函数, 其他元素当作函数的参数。如果其中某个参数也是表, 那就按照同样的原则对这个表求值, 结果再传递给最初的函数作为参数。这就是基本原则.
(* 3 4) ;即计算3乘4
上述的例子中, 所有的表都是当作代码来处理的。怎样把表当作数据来处理呢?
在Lisp中, 我们给表加一个前缀'来表示数据。
(set test '(1 2)) ; test的值为两元素表 (set test (1 2)) ; 错误, 1不是函数
Lisp宏
Lisp通过宏(macro)来做元编程。举个例子,
假设我们要写一个任务表的管理程序, 把任务表数据存到一组文件里, 当程序启动时, 从文件读取这些数据并显示给用户。
在别的语言里(比如说Java), 这个任务该怎么做? 我们会解析XML文件, 从中得出任务表数据, 然后写代码遍历XML树, 再转换为Java的数据结构, 最后再把数据展示给用户。
现在如果用Lisp, 该怎么做?
<todo name = "housework"> <item priority = "high">Clean the hose</item> <item priority = "medium">Wash the dishes</item> <item priority = "medium">Buy more soap</item> </todo>
首先在Lisp我们可以用更简洁的S表达式
(todo "housework" (item (priority high) "Clean the house") (item (priority medium) "Wash the dishes") (item (priority medium) "Buy more soap"))
对于Lisp, 很简单, 直接读出然后解析S表达式就ok, 因为对于Lisp无论代码和数据都可以用S表达式来表示
但理解到这层还没有理解到数据即代码的真谛...
我们可以用宏做出更让人惊讶的事, 是的, 我们让数据本身是可以被执行的
宏的工作方式和函数类似。主要的差别是, 宏的参数在代入时不求值。
(macro-name (+ 4 5))
所以这边作者在上面大篇幅的介绍C的宏, 非常相似, 但是区别是C的宏本身不是C, 功能很弱
而面向过程的编程, 表面上看也是基于function, 但是和Lisp有本质的区别.
对于(item (priority high) "Clean the house"), 虽然是一段数据, 但是Lisp中, 我们也可以把他当作一段代码, 相当于item(priority, note)
也就是, 只要我们实现了item, 就可以直接执行这段数据, 而不需要其他额外的代码
(defmacro item (priority note) `(block (print stdout tab "Prority: " ~(head (tail priority)) endl) (print stdout tab "Note: " ~note endl endl)))
我们创造了一个非常小的有限的语言来打印任务表。这个语言只用来解决特定领域的问题, 通常称之为DSLs(特定领域语言, 或专用领域语言)
所以对于同样一段数据, 我们只需要load不同的DSL就可以有完全不同的解释和逻辑...
用DSL解决问题, 做出的程序精简, 易于维护, 富有弹性。
在Java里面, 我们可以用类来处理问题。这两种方法的差别在于, Lisp使我们达到了一个更高层次的抽象, 我们不再受语言解析器本身的限制, 比较一下用Java库直接写的构造脚本和用Ant写的构造脚本其间的差别。
我是否可以说, Lisp是一种简洁的, 可执行的XML