作为一种紧凑简洁的JVM编程语言,Loop很明显地受到了Haskell、Scheme、Ruby和Erlang的影响,而且它也同时尝试着将函数式语言和面向对象语言的优秀特性实用且一致地整合在一起。
程序会在传输到JVM中进行编译,而且会尽可能地优化,这样在解释执行的时候就不会产生性能损失;所有的这一切优化都是为了产生高效的可执行代码。
Loop的源码文件结构一般如下:
module declaration import declarations functions & type definitions free expressions
下面是一个Loop程序的例子:
module mymodule require othermod require yet.another class Pair -> left: 0 right: 0 main -> new Pair() # 注释可以放在任何地方 #自由表达式必须放在最后 print('mymodule is go!')
InfoQ和Loop的创建者Dhanji R. Prasanna进行了一次小型的问答互动,在这之前,有必要对Dhanji做一下介绍,他是前Google工程师,JAX-RS规范的作者之一,“依赖注入:设计模式”的作者,Manning出版社已经将这本书付梓。
InfoQ:Loop相比其他的JVM语言来说有何不同?
Dhanji:我不想做一个面面俱到的特性比较,不过我觉得,若是阐述Loop的设计理念,这将能更好地回答你的这个问题,Loop是为了让开发者有一个一致、简单而且快乐的编程经历。所有的特性都是经过精心设计,并且设计过程中我非常仔细地考虑了特性之间的交互,不仅仅是句法上,而且还包括了语义。在其他的语言中,你可能有很多方法来完成一件事情,这已经几乎成为一种语言的特性,但是我觉得,多数其他实现都是不需要的。在Loop中,我尝试着对如何完成一件事情做出种种限制,这样便能够保持语言的简洁和简单,从而得到一个富有魅力的,感觉舒适的语法。要知道,阅读和编写代码应该是一个简单愉快的过程。
另外一个区别就是Loop源代码直接编译成为JVM字节码,但是这个编译过程是在传送到JVM的过程中完成。也就是说它看起来非常像是一门脚本语言(而且像Lisp一样是REPL(读取,求值,打印,循环)式),但是它的性能实际上要比真正的解释型语言要好。我可以让其他人做基准测试来验证,不过现在看来,在我做的简单测试中,Loop的速度非常快。我也在启动优化上花费了很多时间和精力,因此它的启动速度能够完全达到JVM的极限,我可以毫不夸张的说,启动速度这个特性通常被大多数JVM语言给忽略了。
Loop也是紧密地和Java结合在一起。在Loop程序中可以很方便地调用Java方法或者使用Java对象。Lists、Sets和Maps都只是java.util的成员,但是做了一些扩展(也简单地扩展了String)。这和其他的语言有区别,它们是维护了了两个不同的库来对Java库进行扩展。
最后,Loop从一开始就内建了对并发的支持,而且将不可变和状态的共享作为不可或缺的特性。
InfoQ:你提到了许多Loop的特性都受到了例如Haskell、Scheme和Ruby这样的语言的影响,你可以简单介绍一些例子吗?
Dhanji:当然,不过要知道,当你说“受到影响”的时候,人们总是趋向于认为“直接抄袭”,并且会仔细地检查你是不是有什么地方抄错了,应付这种想法是非常艰难的。从我看来,直接受到影响的部分是语法。尤其是Haskell的模式匹配,以及“where”和“do”语句块,Scheme的类型系统、模块,TCO(尾调用优化)以及词法结构(闭包),当然还有Ruby的符号以及自由形式的脚本。这里有一个句法结构的典型例子,你可以看到Loop是如何受到这些影响的,看看函数调用可以以一种后缀的形式来使用:
print(36) # 可以写为: 36.print()这看起来的确很像是Ruby的函数调用,但是事实上,这是多态(重载)的函数的简单使用。我发现这种形式能够增强某些代码的可读性,尤其是“扩展”已经存在的Java对象时候。当然,我们也要权衡这种调用的利弊,不过我相信当Loop成熟之时,这个特性将会得到大量应用。
更进一步地说,Loop也还在函数设计的过程中受到了来自于Haskell和Scheme(尤其是后者)的语义影响。一个典型的例子便是从状态性,面向封装的设计转为一个无状态,声明式的设计。像Scheme,Loop在IO的设计上也并不是非常优秀,但是另一方面来看,这也加强了并发程序的不可变性。这个加强之处很明显地受到了Haskell哲学的影响。
不仅如此,Haskell还影响了如何将声明式的代码更易编写和阅读上。我非常喜欢这个哲学理念:代码应该读起来像是一个解,而不是像一个如何在洗衣房洗衣的说明列表;或者说,我们应该强调程序“做什么”而不是“怎么样”,Loop毫无疑问地受到了这种理念的影响。
InfoQ:看起来Loop也花费了非常多的精力在并发支持和内建的消息传递接口上。你可以跟我们解释一下和其他流行的并发技术,Loop的并发支持有什么不同吗?
Dhanji:这是一个非常好的问题。Erlang有许多非常优秀的地方值得借鉴。在Loop中实现并发有两个主要的方法,它们都是Loop原生支持,而且,如果能够相互结合使用将会非常强大:- 消息驱动通道(对消息传递,队列和线程池的面向事件的抽象)
- 软件事务内存(一个用于共享可变状态的无锁的,原子性的一致性的模式)
前者将会管理好所有的细节,你所面临的只是一个抽象的概念。设置好可以并发执行的轻量级的“通道”数目,然后简单地放入一堆任务即可,当然也可以考虑将这些任务分片然后在各个片内,然后串行执行。这其实提供了一个非常简单的方法来创建真正地分片事件队列。因为通道是非常的轻量级,你可以很简单很容易地创建成千上万的通道,然后用来分片执行,例如,按照用户名分片。每个串行通道各自拥有一小块永久的内存,这将会使得增量式任务处理更加容易。
Loop同样保证每个线程都是平均分布在这些通道中,这个所谓的公平参数是可以配置的。所有我现在介绍的特性你现在可以马上在Loop中找到,而且,以后每个串行通道将会有一个可以配置的线程池。
我提到了,串行通道拥有少量的永久私有内存 - 另一方面,事务内存则是一个更强大的选择,如果你熟悉数据库的话,Loop如何使用这个技术是和数据库中的“并发优化”类似。我们再也不需要锁这个概念,即便是在写入的时候。这类内存的优化目标便是超高吞吐量的读操作和无阻塞的写操作。这个已经成为语法的一部分:
update(person) in @person -> this.name: person.name, this.age: person.age注意“in @person”,Loop将会在@person片中执行这个事务
在这个方法中,我将会更新@person“事务片”中的数据。“this”指针指向当前的事务片。当函数完成的时候,当前事务片将会对其他线程原子可见,或者失败之后当做完全没有执行过(类似于回滚)。其他的线程(即使不在这个事务中)都可以看到一个一致的@person片,然后这个片在事务执行的时候会短暂不可见,所有的线程在事务完成之后将会马上看到一个新的对象,无锁,无需等待。这最激动人心的事情便是读和写线程完全无阻塞。
这个特性仍然还是处于Alpha阶段,我尝试着搞定这个语义问题,但是我真的觉得通道API使得Loop中并行编程变得优美,强大而且容易理解。
你也可以在Github上提交你的代码为Loop做贡献。
查看英文原文:Loop: A Compact JVM Language for Multi-Core