博客园  :: 首页  :: 联系 :: 管理

lisp的本质[2]

Posted on 2011-08-17 10:16  雪庭  阅读(339)  评论(0)    收藏  举报

Ant Reloaded

重新审视Ant

 
Now that we've made the trip to the dark side of the moon, let's not leave quite yet. We may still learn something by exploring it a little more, so let's take another step. We begin by closing our eyes and remembering a cold rainy night in the winter of 2000. A prominent developer by the name of James Duncan Davidson1 was hacking his way through Tomcat servlet container. As the time came to build the changes he carefully saved all his files and ran make. Errors. Lots of errors. Something was wrong. After careful examination James exclaimed: "Is my command not executing because I have a space in front of my tab?!" Indeed, this was the problem. Again. James has had enough. He could sense the full moon through the clouds and it made him adventurous. He created a fresh Java project and quickly hacked together a simple but surprisingly useful utility. This spark of genius used Java property files for information on how to build the project. James could now write the equivalent of the makefile in a nice format without worrying about the damned spaces ever again. His utility did all the hard work by interpreting the property file and taking appropriate actions to build the project. It was neat. Another Neat Tool. Ant.

我们现在已经来到了月亮背光的那一面, 先别忙着离开。再探索一下, 看看我们还能发现什么东西。闭上眼睛, 想一想2000年冬天的那个雨夜, 一个名叫James Duncan Davidson的杰出的程序员正在研究Tomcat的servlet容器。那时, 他正小心地保存好刚修改过的文件, 然后执行make。结果冒出了一大堆错误, 显然有什么东西搞错了。经过仔细检查, 他想, 难道是因为tab前面加了个空格而导致命令不能执行吗? 确实如此。老是这样, 他真的受够了。乌云背后的月亮给了他启示, 他创建了一个新的Java项目, 然后写了一个简单但是十分有用的工具, 这个工具巧妙地利用了Java属性文件中的信息来构造工程, 现在James可以写makefile的替代品, 它能起到相同的作用, 而形式更加优美, 也不用担心有makefile那样可恨的空格问题。这个工具能够自动解释属性文件, 然后采取正确的动作来编译工程。真是简单而优美。

After using Ant to build Tomcat for a few months it became clear that Java property files are not sufficient to express complicated build instructions. Files needed to be checked out, copied, compiled, sent to another machine, and unit tested. In case of failure e-mails needed to be sent out to appropriate people. In case of success "Bad to the Bone" needed to be played at the highest possible volume. At the end of the track volume had to be restored to its original level. Yes, Java property files didn't cut it anymore. James needed a more flexible solution. He didn't feel like writing his own parser (especially since he wanted an industry standard solution). XML seemed like a reasonable alternative. In a couple of days Ant was ported to XML. It was the best thing since sliced bread.

使用Ant构造Tomcat之后几个月, 他越来越感到Java的属性文件不足以表达复杂的构造指令。文件需要检出, 拷贝, 编译, 发到另外一台机器, 进行单元测试。要是出错, 就发邮件给相关人员, 要是成功, 就继续在尽可能高层的卷(volumn)上执行构造。追踪到最后,卷要回复到最初的水平上。确实, Java的属性文件不够用了, James需要更有弹性的解决方案。他不想自己写解析器(因为他更希望有一个具有工业标准的方案)。XML看起来是个不错的选择。他花了几天工夫把Ant移植到XML,于是,一件伟大的工具诞生了。

So how does Ant work? It's pretty simple. It takes an XML file with specific build instructions (you decide if they're data or code) and interprets them by running specialized Java code for each XML element. It's actually much simpler than it sounds. A simple XML instruction like the one below causes a Java class with an equivalent name to be loaded and its code to be executed.
 
Ant是怎样工作的?原理非常简单。Ant把包含有构造命令的XML文件(算代码还是算数据,你自己想吧),交给一个Java程序来解析每一个元素,实际情况比我说的还要简单得多。一个简单的XML指令会导致具有相同名字的Java类装入,并执行其代码。
<copy todir="../new/dir">
   
<fileset dir="src_dir" />
</copy>


The snippet above copies a source directory to a destination directory. Ant locates a "copy" task (a Java class, really), sets appropriate parameters (todir and fileset) by calling appropriate Java methods and then executes the task. Ant comes with a set of core tasks and anyone can extend it with tasks of their own simply by writing Java classes that follow certain conventions. Ant finds these classes and executes them whenever XML elements with appropriate names are encountered. Pretty simple. Effectively Ant accomplishes what we were talking about in the previous section: it acts as an interpreter for a language that uses XML as its syntax by translating XML elements to appropriate Java instructions. We could write an "add" task and have Ant execute it when it encounters the XML snippet for addition presented in the previous section! Considering that Ant is an extremely popular project, the ideas presented in the previous section start looking more sane. After all, they're being used every day in what probably amounts to thousands of companies!
 
这段文字的含义是把源目录复制到目标目录,Ant会找到一个"copy"任务(实际上就是一个Java类), 通过调用Java的方法来设置适当参数(todir和fileset),然后执行这个任务。Ant带有一组核心类, 可以由用户任意扩展, 只要遵守若干约定就可以。Ant找到这些类,每当遇到XML元素有同样的名字, 就执行相应的代码。过程非常简单。Ant做到了我们前面所说的东西: 它是一个语言解释器, 以XML作为语法, 把XML元素转译为适当的Java指令。我们可以写一个"add"任务, 然后, 当发现XML中有add描述的时候, 就执行这个add任务。由于Ant是非常流行的项目, 前面展示的策略就显得更为明智。毕竟, 这个工具每天差不多有几千家公司在使用。

So far I've said nothing about why Ant actually goes through all the trouble of interpreting XML. Don't try to look for the answer on its website either - you'll find nothing of value. Nothing relevant to our discussion, anyway. Let's take another step. It's time to find out why.
 
到目前为之, 我还没有说Ant在解析XML时所遇到困难。你也不用麻烦去它的网站上去找答案了, 不会找到有价值的东西。至少对我们这个论题来说是如此。我们还是继续下一步讨论吧。我们答案就在那里。
 
Why XML?
为什么是XML
 
Sometimes right decisions are made without full conscious understanding of all the issues involved. I'm not sure if James knew why he chose XML - it was likely a subconscious decision. At the very least, the reasons I saw on Ant's website for using XML are all the wrong reasons. It appears that the main concerns revolved around portability and extensibility. I fail to see how XML helps advance these goals in Ant's case. What is the advantage of using interpreted XML over simple Java source code? Why not create a set of classes with a nice API for commonly used tasks (copying directories, compiling, etc.) and using those directly from Java source code? This would run on every platform that runs Java (which Ant requires anyway), it's infinitely extensible, and it has the benefit of having a more pleasant, familiar syntax. So why XML? Can we find a good reason for using it?

有时候正确的决策并非完全出于深思熟虑。我不知道James选择XML是否出于深思熟虑。也许仅仅是个下意识的决定。至少从James在Ant网站上发表的文章看起来, 他所说的理由完全是似是而非。他的主要理由是移植性和扩展性, 在Ant案例上, 我看不出这两条有什么帮助。使用XML而不是Java代码, 到底有什么好处? 为什么不写一组Java类, 提供api来满足基本任务(拷贝目录, 编译等等), 然后在Java里直接调用这些代码? 这样做仍然可以保证移植性, 扩展性也是毫无疑问的。而且语法也更为熟悉, 看着顺眼。那为什么要用 XML呢? 有什么更好的理由吗?
 
It turns out that we can (although as I mentioned earlier I'm not sure if James was consciously aware of it). XML has the property of being far more flexible in terms of introduction of semantic constructs than Java could ever hope to be. Don't worry, I'm not falling into the trap of using big words to describe incomprehensible concepts. This is actually a relatively simple idea, though it may take some effort to explain. Buckle your seat-belt. We're about to make a giant leap towards achieving nirvana.

有的。虽然我不确定James是否确实意识到了。在语义的可构造性方面, XML的弹性是Java望尘莫及的。我不想用高深莫测的名词来吓唬你, 其中的道理相当简单, 解释起来并不费很多功夫。好, 做好预备动作, 我们马上就要朝向顿悟的时刻做奋力一跃。

How can we represent 'copy' example above in Java code? Here's one way to do it:
 
上面的那个copy的例子, 用Java代码怎样实现呢? 我们可以这样做:
 
CopyTask copy = new CopyTask();
Fileset fileset 
= new Fileset();

fileset.setDir(
"src_dir");
copy.setToDir(
"../new/dir");
copy.setFileset(fileset);
 
copy.excute();

The code is almost the same, albeit a little longer than the original XML. So what's different? The answer is that the XML snippet introduces a special semantic construct for copying. If we could do it in Java it would look like this:
 
这个代码看起来和XML的那个很相似, 只是稍微长一点。差别在那里? 差别在于XML构造了一个特殊的copy动词, 如果我们硬要用Java来写的话, 应该是这个样子:
 
copy("../new/dir");
{
    fileset(
"src_dir");
}

Can you see the difference? The code above (if it were possible in Java) is a special operator for copying files - similar to a for loop or a new foreach construct introduced in Java 5. If we had an automatic converter from XML to Java it would likely produce the above gibberish. The reason for this is that Java's accepted syntax tree grammar is fixed by the language specification - we have no way of modifying it. We can add packages, classes, methods, but we cannot extend Java to make addition of new operators possible. Yet we can do it to our heart's content in XML - its syntax tree isn't restricted by anything except our interpreter! If the idea is still unclear, consider introducing a special operator 'unless' to Java:

看到差别了吗? 以上代码(如果可以在Java中用的化), 是一个特殊的copy算符, 有点像for循环或者Java5中的foreach循环。如果我们有一个转换器, 可以把XML转换到Java, 大概就会得到上面这段事实上不可以执行的代码。因为Java的技术规范是定死的, 我们没有办法在程序里改变它。我们可以增加包, 增加类, 增加方法, 但是我们没办法增加算符,而对于XML, 我们显然可以任由自己增加这样的东西。对于XML的语法树来说, 只要原意,我们可以任意增加任何元素, 因此等于我们可以任意增加算符。如果你还不太明白的话,看下面这个例子, 加入我们要给Java引入一个unless算符:
 
unless(someObject.canFly())
{
    someObject.transportByGround():
}

 In the previous two examples we extend the Java language to introduce an operator for copying files and a conditional operator unless. We would do this by modifying the abstract syntax tree grammar that Java compiler accepts. Naturally we cannot do it with standard Java facilities, but we can easily do it in XML. Because our XML interpreter parses the abstract syntax tree that results from it, we can extend it to include any operator we like.

在上面的两个例子中, 我们打算给Java语法扩展两个算符, 成组拷贝文件算符和条件算符unless, 我们要想做到这一点, 就必须修改Java编译器能够接受的抽象语法树, 显然我们无法用Java标准的功能来实现它。但是在XML中我们可以轻而易举地做到。我们的解析器根据 XML元素, 生成抽象语法树, 由此生成算符, 所以, 我们可以任意引入任何算符。

For complex operators this ability provides tremendous benefits. Can you imagine writing special operators for checking out source code, compiling files, running unit testing, sending email? Try to come up with some. If you're dealing with a specialized problem (in our case it's building projects) these operators can do wonders to decrease the amount of code you have to type and to increase clarity and code reuse. Interpreted XML makes this extremely easy to accomplish because it's a simple data file that stores hierarchical data. We do not have this option in Java because it's hierarchical structure is fixed (as you will soon find out, we do have this option in Lisp). Perhaps this is one of the reasons why Ant is so successful?
 
对于复杂的算符来说, 这样做的好处显而易见。比如, 用特定的算符来做检出源码, 编译文件, 单元测试, 发送邮件等任务, 想想看有多么美妙。对于特定的题目, 比如说构造软件项目, 这些算符的使用可以大幅减低少代码的数量。增加代码的清晰程度和可重用性。解释性的XML可以很容易的达到这个目标。XML是存储层次化数据的简单数据文件, 而在Java中, 由于层次结构是定死的(你很快就会看到, Lisp的情况与此截然不同), 我们就没法达到上述目标。也许这正是Ant的成功之处呢。
 
I urge you to take a look at recent evolution of Java and C# (especially the recently released specification for C# 3.0). The languages are being evolved by abstracting away commonly used functionality and adding it in the form of operators. New C# operators for built-in queries is one example. This is accomplished by relatively traditional means: language creators modify the accepted abstract syntax tree and add implementations of certain features. Imagine the possibilities if the programmer could modify the abstract syntax tree himself! Whole new sub-languages could be built for specialized domains (for example a language for building projects, like Ant). Can you come up with other examples? Think about these concepts for a bit, but don't worry about them too much. We'll come back to these issues after introducing a few more ideas. By then things will be a little more clear.

你可以注意一下最近Java和C#的变化(尤其是C#3.0的技术规范), C#把常用的功能抽象出来, 作为算符增加到C#中。C#新增加的query算符就是一个例子。它用的还是传统的作法:C#的设计者修改抽象语法树, 然后增加对应的实现。如果程序员自己也能修改抽象语法树该有多好! 那样我们就可以构造用于特定问题的子语言(比如说就像Ant这种用于构造项目的语言), 你能想到别的例子吗? 再思考一下这个概念。不过也不必思考太甚, 我们待会还会回到这个题目。那时候就会更加清晰。