ANI:比C更快,比Java更安全,比*sh更简单?
2010 1 12 01:07 AM 1913次查看
分类:编程 标签:无
看到这种宣传口号,JavaEye上自然是吵成一片。而我也自然不能视而不见,马上去看了下它的文档,立马被它的优美折服了,有种不介绍不足以平民愤的感觉。
首先说下我对它的整体感觉吧,没兴趣的就不用继续往下看了:
- 并发无处不在:每条普通的语句都能并发执行,这就是号称比C快的原因(C不内置并行执行的功能)。
- 语句就是管道处理:熟悉Unix编程的自然对管道不陌生,这样学习起来就很快;而由于代码执行顺序是基于管道,所以只要管道没有阻塞都能乱序地并行执行,看上去很疯狂。
- 没有关键字:虽然文档中没有说出这点,但我读完也没发现一个关键字;取而代之的是很多操作符,这也导致了阅读的困难。虽然不适合向不懂程序的人介绍你的代码,但是用来向同行炫耀也足够了(至少比Perl好学)。
- 一切都是对象:这和Python也一样,没什么好说的。
- 编译型语言:不能像大多数脚本语言一样边编写边执行,不过这也保证了其速度。
- 文档还不完整:读得正过瘾时没了,不过开发者有限(Google Code上只列出了1个),这也是没办法的。
这个已经向作者提出了,还没回复,所以我也没法进行测试了,关心其性能的就不用继续看了,下面只介绍它的语法。
由于文档不完整,这里只介绍已写完的几个部分:
管道
正如前文所述,ANI程序是由管道组成的。管道就是数据流,而数据是封装在对象里的,所以对象之间的数据传递(流动)就是通过管道了。
首先通过hello world来了解管道:
"Hello, World!" ->std.out
这里的"Hello, World!"是一个字符串对象,它通过管道传给(->操作符)标准输出(std.out),于是屏幕上就显示“Hello, World!”了。这里并没有使用分号(;)来表明语句结束,因为ANI的语句结束是不需要分号的。
但是分号有个另外的作用:它表示管道的结束。那么管道结束有什么作用呢?
ANI中最神奇的一点就在于:多个对象通过多个管道是并行的。所以多个管道如果没有使用相同对象的话,那么就可以并行执行。
简单来说,这样就可以实现2条语句并行执行了:
语句1;
语句2;
有没有被ANI的并行吓到?下面继续来说如何对对象进行加锁。锁存器
看看这段代码,它同样也是一个hello world程序:
s = [[string\]];
\s ->std.out;
"Hello, World!" ->s;
第一行代码用了个奇怪的方括号符号,实际上据我理解,这就相当于C++里的new操作符。若按原文来说的话,[[x]]表示创建一个类型为x的匿名对象,=则是将这个对象绑定到一个变量名。但是那个反斜线(\)是啥呢?它就是ANI中一个很重要的东西:latch。为了便于理解,下文中我就翻译成锁存器了。
锁存器有一个重要的特性:当管道尝试获取锁存器里的对象时,如果锁存器里是空的,它就会等待,直到锁存器里重新被填入对象。正是由于锁存器的存在,对象才能以这种简单的方式加解锁。
因此第一行代码就是将s绑定到一个string对象的锁存器上。
第二行代码也用了个反斜线,它写在了s前面,这表示从s锁存器里取出一个对象(虽然作者用的术语是delatch或unlatch,但实际上还附带了一个加解锁效果),通过管道传给标准输出。
第三行代码则将"Hello, World!"这个字符串传给s锁存器。
请注意每行代码都以分号结束,所以它们是不同的管道,那么你看出这段代码真正的运行顺序了吗?
没错,第一行代码可以立即执行;而第二行代码在取s里的对象时,发现s是空的,所以会等待;第三行代码本来是和第一行代码并行执行的,但是发现s还没定义,所以会等到第一行代码执行完才执行;当第三行代码执行完时,第二行代码的管道发现s不为空了,于是继续执行,输出"Hello, World!"。
因此,这个程序正确的执行顺序是1、3、2。这个执行顺序是由编译器来安排的,而与代码的书写顺序无关,所以无论你怎么打乱这些代码的书写顺序,它们都会得到相同的执行结果。
是不是觉得ANI很巧妙,但似乎还缺了些什么?
没错,连数组和for语句都没有,怎么能叫一门编程语言呢?别急,下面就告诉你没有数组和for,照样可以编程。
流
提到流,立刻就会想到数据流、文件流等一些名词。但无论怎样的流,它都有个共同的特性:先进先出(FIFO)。ANI里的流和Python里的list对象很像,当然你也可以当成数组,所以就不需要一个专门的数组类型了。
下面来看一段代码,它会输出10次hello world:
index = [[int\\]] <- {0,1,2,3,4,5,6,7,8,9};
\\index ("Hello, World #" + .. + "!\n") ->std.out;
第一行代码中有个[[int\\]],这表示创建一个int类型的流。这个语法很像创建锁存器,所不同的是流可以传入多个对象,而锁存器在同一时间只能保存一个对象。接着,我们将这个流绑定到index上。
然后,我们将0到9这10个数传入这个流;你可能会说这10个数就是一个数组啊,不过作者是将它称为列表…
第二行代码有个\\index,这个\\操作叫作destream、flush或stream injection,也就是把流中的对象依次取出来,直到流为空。
取出来的对象放在哪呢?你会注意到后面的("Hello, World #" + .. + "!\n"),那2个小数点(..)是一个回调占位符,表示取出来的对象应该放在那。
括号里的语句执行完是一个字符串,它通过管道传递给标准输出,显示在屏幕上。
很明显,这个destream操作就相当于Python的for...in语句,它遍历index里的所有对象,传给回调占位符,并输出给标准输出。
当然,这段代码有个很不好看的地方:0到9是手动写的,而不是程序自动生成的,所以继续来修改这个程序:
\\[[std.gen]]<-10 ("Hello, World #" + .. + "!") ->std.out;
这里先作弊一下吧,我们用标准的std.gen对象来生成一个序列。实际上读到后面,你可以很快地写出一个std.gen的实现,但Python程序员却需要很多代码才能用C写出一个高性能的range实现。目前你只要知道,当把10传给一个std.gen对象时,会得到一个包含0到9的流。
接着我们对这个流进行destream,其后的操作就和前面的代码一样了。
虽然我们只看了几个hello world程序,但实际上我们已经学得够多了,不信你把刚才所学的最后一行代码给别人看,肯定回答你是天书,充其量就是一门垃圾语言而已。
说实话叫我一眼看出这行代码干了什么,我也做不到,这就是不用关键字,而用大量符号带来的坏处。
不过如果你对其任充满好奇,那么就继续往下看吧。
锁存器 vs 流
这一节会提到流的另外几个特性,于是先上代码:
a = [[int\]] <- 0; // integer latch initially containing a 0
b = [[int\\]] <- {1,2,3}; // integer stream initially containing {1,2,3}
c = [[int\\]] <- {4,6,7}; // another integer stream initially containing {4,6,7}
x = [[int\]] <- 5; // another integer latch initially containing 5
y = [[int\\]] <- {8,9}; // finally, a stream initially containing {8,9}
/* main output pipe */
\a ->std.out /* 1 */
\\b ->std.out /* 2 */
\c :: \x ->std.out /* 3 */
\\c ->std.out /* 4 */
\y ->std.out /* 5 */
\y ->b; /* 6 */
注意到注释的格式是和C一样的,我就不解释了。前面的绑定(赋值)语句也没有什么好说的,于是从下面开始说。
值得注意的是,后一部分的代码并没写分号,所以是依次执行的。
第一行是对a进行delatch操作,即从a中取出0,传递给标准输出。
第二行是对b进行destream操作,即从b中取出1到3,传递给标准输出。
第三行有点复杂,拆开来说。
\c表示对c进行delatch操作。c虽然是一个流,但仍能执行这个操作,效果就是取出流的第一个对象,即4;此时c里还剩6和7。
\x则是对x进行delatch操作,拿到5。
::是一个锁存器连接操作符,表示将\c和\x合并起来,作为一个整体传给标准输出,因此会输出4和5。这个符号有点奇怪,自己适应下吧…
第四行是对c进行destream操作,即从c中取出6和7(4已经被取出,也就不存在了),传递给标准输出。
第五行是对y进行delatch操作,即从y中取出8,传递给标准输出。
第六行是很关键的一句,它对y进行delatch操作,即从y中取出9,传递给b。注意到在第二行我们就将b这个流传递给标准输出了,所以执行到这时,b仍然会很负责地将9传给标准输出。
也就是说,当流执行了destream操作后,一旦它被填入了新的对象,它就会隐式地将这些对象也进行输出。
由于流有这样一个特性,我们就可以用它来实现循环(或递归),于是进入下一节。
循环
这一节展示的代码很漂亮,你或许会想到什么类似的语言:
n = [[int\\]] <- 0;
\\n < 10 ? {
("Hello, World #" + .. + "!\n") ->std.out;
(.. + 1) ->n;
};
第一行就不用解释了吧,于是进入第二行。\\n表示对n进行destream,这里我们暂时会拿到0。
接着是一个问号(?)操作符,它有点类似C里的三元操作符(? :),用来代替if语句。这段代码表示:如果\\n < 10成立,就执行大括号({})里的代码。
而这个大括号里的代码就称为block,是不是觉得和Ruby很像,甚至更为简洁?
第三行的..仍然是回调占位符,此处暂时是替代0。
第四行我们将..和1相加,暂时是拿到1,并传给n这个流。这个操作就导致了n不为空,所以n继续destream操作,只是这次是用1代替。
这样n就会一直进行destream操作,直到取出来的是10,不符合\\n < 10这个条件,block就不再执行,循环也就结束了。
而从原理上来说,这是一个尾递归的过程,因此LISP程序员应该不会陌生。
那么这种循环方式有什么好处呢?
请注意block中的分号,没错,一切都是并行的。
也就是说,当你从n中取出一个数,并用于输出hello world时,你同时也把这个数+1给计算了出来,并填入了n。
是不是觉得ANI很疯狂?至少我从来没想过用另一个线程去递增循环变量;因为一般的语言要做这种事太麻烦了,可ANI在不知不觉中就帮你实现了。
文档就暂时到此为止了,后面的就等待作者更新了。
不得不说我被ANI折服了,虽然肯定不会像Python一样频繁地去使用它,但是这种新颖的程序模型的确是很吸引我,我也希望它会有个很好的发展(至少能在XX大学里开设一门讲述它的课程也好)。
向下滚动可载入更多评论,或者点这里禁止自动加载。