JVM 语言 Ioke:直观的语法,Lisp 与 Ruby 的力量

  • Dionysios G. synodinos
  • 张龙

2008 年 11 月 28 日

话题:JavaRuby编程语言JVM语言 & 开发架构

核心的 JRuby 开发者及书籍Practical JRuby on Rails Projects的作者Ola Bini为 JVM 开发了一种名为Ioke的新语言。这种强类型、动态、基于原型的面向对象语言的目标在于给予开发者 Lisp 与 Ruby 的力量,同时其拥有优雅、小巧及规则的语法。

Ola 解释了Ioke 的基本特性

Ioke 是一个强类型、动态、基于原型的面向对象语言。它很容易理解并且内置了对几种宏的支持。对 Ioke 产生重要影响的语言有IoSmalltalkSelfRubyLisp(尤其是Common Lisp)。

Ioke 目前构建在 JVM 上,但我现在正考虑将其编译为 JavaScript 并在 V8 上运行它。

对于 Ioke,我有几个目标,但最明确的一个是创建一种能将 Ruby 和 Lisp 中我所喜欢的那些特性组合起来的语言。结果我发现 Io 已经实现了我所期望的大多数特性,但有些地方实现的还不完美。我还需要一种适合表达内部 DSLs 的语言。我想要一种不会妨碍我,反而会助我完成工作的语言。根据以上几点,我设计出了一个宏系统,有些人可能会觉得这个系统很差劲。

就 Ioke 的设计 InfoQ 向 Ola Bini 提出了几个问题:

InfoQ:看起来 Ioke 的关键特性之一就是它使用了基于原型的继承。相比于占主导地位的基于类的继承,你认为这种继承方式更强大么?

是的,这是 Ioke 的一个特性,但我不确定它是否是一个关键特性。它在很大程度上改变了一些东西的设计。我对此感到很满意,我认为它实际上比基于类的系统要好,既然开发 Ioke 的主要目的是为了我自己的使用,那么我的感觉当然就很重要了。

在 Ruby 中,你可以使用单例类实现基于原型的 OO,同时我发现可以用这种方式清晰地对一些算法建模。从功能上来说这没有什么明显的问题,只要你想,你总可以根据规约实现基于类的 OO。

Ioke 的一个主要指导原则是我所采取的决定并不是为了获得大家的认可。仅仅因为基于类的 OO 占据着主导地位我就要使用它么? 不一定吧。确实有很多原因要求我们使用基于类的 OO,然而历史已经证明对很多应用来说这样做的意义并不大。因此在 Ioke 的开发过程中我尝试了各种可能。

InfoQ:因为在主流的语言中只有 JavaScript 采取了基于原型的继承,你认为这种形式会被大家所理解并应用到实践中么?

实际上我认为基于原型的 OO 要比基于类的 OO 更自然,也更容易理解。我觉得基于类的 OO 是需要学习的,而大多数人都会发现基于原型的 OO 更加直观,前提是他们并没有被人灌输基于类的 OO 的所谓优点。当然,JavaScript 可能并不是最好的参照物,因为语言的基于原型的本质很容易被掩盖在该语言模型的边边角角之下,这意味着大多数开发者实际上并不知道如何以正确的方式使用语言的这些特性。

InfoQ:看起来 Ioke 从一开始就被设计成一种 JVM 语言。你认为在最近一段时期内这会成为新语言的必经之路么?

我现在的想法是没必要从头构建一个新的虚拟机。例如,大多数新语言都带垃圾回收,但我不理解为什么创建这些新语言的人要编写自己的 GC 呢。这需要花费数月的时间,而它只是一项重复的工作而已。看看 Ruby GC 的那些问题吧。显然这种想法对很多其他的事情也适用——尤其是库。因此 Ioke 是一个 JVM 语言(但是 Ioke 的大部分内容是不依赖于 JVM 的。你可以在另一个平台上重新实现这些内容,这很简单。核心内容非常小)。我认为面向 JVM、CLR、Parrott 及 LLVM 的语言都应该这样。从头构建一个新的虚拟机几乎没有任何意义。

InfoQ:你为 Ioke 实现的条件(Condition)系统看起来与 Java 的异常处理系统很相似,但更灵活。你能否提供几个例子来更好的说明其价值呢?

你可以认为异常所具有的功能是条件系统的一个子集。有两点区别:其一是从协议和抽象的角度来看。异常所处理的东西不见得非得是异常或是错误,警告也行。大多数动态语言都有基于 ad-hoc 日志的警告系统。但是如果你想做其他事情呢?在 Ruby 中,你可以改变 warn 方法以抛出一个异常。然而这仍然表示警告和异常的处理方式存在着分歧。要么采取系统拦截、要么采取线程拦截。所有这一切仅仅是表面上的不同而已,本质上是一回事。

条件可以将这一切统一起来。他们为上面提到的那些事情提供了一致的协议。

条件所提供的功能是双重的。首先就是 restart,它实际上几乎是完全独立的。

所谓 restart 就是可以注册到块中的一些东西。它基本上就是调用 restart 时所执行的一些代码块。然后有一些方法会去调用命名的 restart,找到所有活动的 restart。restart 几乎可以看作是一种异常机制。从范围上来说它是动态的。

凭借条件我们可以为某些可能发生的事情注册处理器。当条件发生时,处理器可以选择去处理它或是把它交给下一个处理器处理。然而这并不是堆栈展开(unwinding the stack)(至少现在不是)。如果某个处理器想去处理某个条件(处理器也是一块代码),处理器上下文中相应的代码就会被执行,执行的位置是动态的,就在条件第一次发生的地方。这意味着几个方法从某个条件发生的地方所调用的处理器实际上可以在相应的上下文中进行叠加。

这没什么好奇怪的,你可以在纯 Ruby 环境下完成这件事。但如果标准库没有提供相应的支持,那效果就要大打折扣了。

在 Common Lisp 中,这非常强大。当你以交互的方式使用 Common Lisp 时,条件的默认处理器会将你带到调试器中。该调试器运行在错误发生的上下文中,同时它可以完成处理器所能完成的事情——包括为变量提供新的值等等。该调试器无需做任何特别的事情,实际上它只是条件系统一个具体的用例而已。

这实在是太强大了。

InfoQ:你认为 Ioke 符合维护和重构的标准么?它是动态的,又具有强类型,你是怎么看的?

这很难说。既然它很简洁,同时又为这种简洁性提供了强大的特性,那么它应该很容易维护。同样的原因,自动化的重构现在还不完善。

就像 Lisp 一样,Ioke 提供了语法抽象。有两种形式,第一种是宏,它就像是具有延迟参数的方法调用一样,这些参数可用特殊的方式计算出来。另一种是语法,它与 Common Lisp 的 defmacro 差不多。这两种方式为创建新的控制结构和定义新的抽象提供了可能。语言本身是足够强大的,你可以用其创建自己的方法类型。如果你不喜欢关键字参数,你可以定义一种新的不包含关键字参数的方法类型。当前 Ioke 中的 DefaultMethod 可以用纯 Ioke 实现出来,使用宏就行。

InfoQ:对于 Ioke 的语法来说,你遵循了 Lisp 和 Smalltalk 的方式,例如,space 的使用。一些人可能会觉得这么做会令那些熟悉 C 语言代码风格的开发者敬而远之。你觉得是这样的么?

很早以前我就已经是一个 C、C++ 及 Java 开发者了,我并没有觉得哪里不舒服。Ioke 的语法确实很麻烦,之前有很多人都觉得这对于强大的抽象来说是个绊脚石。当你有一个像以上那些语言的 AST 时,你会发现要想实现语法宏是多么的不方便。语法占很大的比重,因为他们很不统一。

因此大家可能在一开始会觉得它不那么自然,但我真的很喜欢它,我相信你也会的。比如我发现 Ioke 的可读性就非常好,而 Java 就不行了。Ioke 中没有太多的标点字符妨碍我们阅读。下面对 Java、Ruby 及 Ioke 进行了一下比较:

Arrays.asList("foo", "bar", "quux")
	.map(new Function<String, Pair<String, Int>>(){
	public Pair<String, Int> call(String str){
	return new Pair<String, Int>(str, str.length());
	}}).select ..... // this gets too long, ok?
	
["foo", "bar", "quux"].map {|str| [str, str.length]}.select {|n|  n[1] > 3}
	
["foo", "bar", "quux"] map(str, [str, str length]) select(second > 3)

在这个例子中,Ruby 的区别不那么明显,但它实际上也有很大的差别。我发现当方法用空格分隔开时阅读起来会更方便。使用圆点来终止表达式同样会造成很大的差别。

因此,作为 Lisp 的使用者,这么说有点另类,但语法真的是很重要。我为 Ioke 设定的目标就是让其拥有 Lisp 和 Ruby 的力量,同时保持其语法优雅、小巧及规则。

如果你对 Ioke 的开发感兴趣,你可以从Ola’s git 的仓库上得到其源码或者是访问其项目主页

你还可以从 InfoQ 上得到关于编程语言JVMRuby方面的更多信息。

查看英文原文:The Ioke JVM Language: The power of Lisp and Ruby with an intuitive syntax

JavaRuby编程语言JVM语言 & 开发架构