你是否曾对软件编程栈的混乱感到过困惑?错综复杂和臃肿的解决方案无处不在,但无论是在当代软件的构建还是维护环节,很少有架构师和程序员能真正意识到由此带来的成本。Red的存在正是为了反击这种复杂性,这是它最主要的设计目的。没错,在现代软件世界中,“简单”的工具和简单的解决方案仍是可以企及的。这是个所有人都竭力掖藏的行业秘密(这可不是什么好事儿),但自1997年以来,解决方案其实已由Rebol编程语言给出。Red继承自Rebol谱系,并尝试将Rebol原先的使用范围加以大大拓展。Red是个雄心壮志的项目,它想要成为首个全栈编程语言。以下是其主要特性和设计目标:
Red语言,正如Rebol一样,依靠独特的途径来编程,与目前主流语言的主张有所不同。以下是JSON发明者Douglas Crockford对Rebol语言发表的评论:
Rebol是一种更具现代性的语言,但也有些与Lisp语言非常相似的思想蕴涵在内,主要体现在它也是完全建立在先进行数据表示、尔后将其作为程序执行起来这样的基础之上。但它拥有更丰富的语法材料。Rebol是一种才华横溢的语言,它本该更普及一些,现在却并未如此,这可引为憾事。
Red和Rebol的设计目标是要尽可能地提供表达能力,同时保持源代码的高可读性,事实上它们的源代码很接近自然语言。2013年的一项将程序设计语言按表达能力排序的研究项目中,Rebol语言名列第三,仅次于Augeas和Puppet这两种DSL(http://goo.gl/qyPXx)。这有力地表明,在所有通用型程序设计语言中,Rebol是表达能力最强的一个。Red和Rebol都遵守务实设计原则,没有哪部分是拍脑袋的决定,或是“为了不同而不同”。每个语法或语义背后,都有明确的设计理由。
Red和Rebol如何在企及如此水平的效率的同时,又保持“简单”呢?根本的原因之一,是因为这两者都既是一种数据格式,又是一门程序设计语言。此特性继承自Lisp,特别是s-表达式概念。Lisp中也建立了Red和Rebol所依赖的元编程模型。它们都是同像语言,所有的代码都表示为数据。值存储在块中(使用方括号记法:[……]),即普通列表,这和Lisp的记法一样,但它们不要求函数调用的那对括号,这就使得代码看上去更像自然语言。函数在默认情况下采用前缀记法,然而,Red和Rebol也支持针对数学和布尔运算符采用中缀表示法,这为代码阅读提供了便利。采用这种纯元编程进行程序设计,反射、热补丁、甚至即时编码这些语言特性都能得到内建支持,不需要学习任何新API,也无须特别的库支持,只使用基本函数进行数据操作即可。以下是使用Red语言REPL的示例:
$ red red>> code: ["hello"] == ["hello"] red>> insert code 'print == ["hello"] red>> code == [print "hello"] red>> do code hello red>> code/2: 123 == 123 red>> code == [print 123] red>> append code [+ 1] == [print 123 + 1] red>> do code 124 red>> length? code == 4 red>> type? code/1 == word! red>> foreach value code [probe type? value] word! integer! word! integer!
如君所见,该语言的基本构件如下:
这些就是用来表示数据、构建表达式、定义函数、创建对象、以及构造更复杂数据类型的基本构件。符号都是一等(first-class)值,也是一种更自然也更有效的字符串替代品,这对于很多用例都适用,尤其是涉及查找操作时就更能说明问题,因为符号可以以O(1)的效率比较,而字符串则要求O(n)。符号不区分大小写,所以print、Print和PRINT是等同的,人类语言不也是这样吗?
Rebol或Red程序源代码通常都是UTF-8输入字符串,它们都会被LOAD化处理。LOAD是原生的核心功能,能够将任何字符串变换成包含在一个块中的内存二进制格式。块将值存储至128比特的相邻单元内。值主要分为标量值(其尺寸固定)或序列值(用户可以向其中添加/删除数据)。数字、日期、元组和值对是标量值的,而值块、字符串、URL、路径、文件和标签是序列值的示例。标量值通常可以置入单个存储单元,而序列值则需要额外内存。
因此,块是带有垃圾回收的内存管理器进行保留和回收的主要分配单位。Rebol采用经典的stop-the-world标记和清理垃圾回收算法,而Red则依赖于stop-the-thread分代压缩垃圾回收算法(尚未完全实现),交替进行部分或全部遍扫。Red将扩充其垃圾回收算法,在未来实现增量回收,以使得它可以用来开发实时应用,例如60-FPS的街机游戏。
一旦源代码被加载入内存,变成一个值块,它就只是纯粹的数据。默认地,在遍扫源代码,使之变换成Red或Rebol二进制文件时,就会对值进行计算。如果是用Rebol语言,加载的块会被解释执行;如果是用Red语言,它们就将被编译成本地代码,但目的都是为了完成计算。然而,值块以何种方式进行解释,则是依赖于语境的。解释方式有如下几种:
默认的(非纯的)、以函数式方式解释的语言(即我们通常所谓的“代码”),是一种面向表达式的语言,它带有很简单的语义规则:
因此,浮在语言上方的函数层可以很容易地被任何自定义计算体取代(例如,撰写一个类似于Prolog的解释器将非常简单)。这使得Red和Rebol具备了极好的可延展性,十分容易适应任何你可能的需要。这种灵活性是DSL得以成为一种自然的方式解决一些计算任务,通过提供特定领域的微语言对于给定的任务实现优化的基础。Red和Rebol凭借这种力量,在核心语言和标准库中广泛地使用了嵌入式DSL:
使得嵌入式DSL的创建容易、便捷的因素有:
下面是一些在Rebol2中的GUI DSL的示例:
按一个大小为100×100的红色按钮,以打开一个窗口:
>> view layout [button "Hello" red 100x100]
显示会触发一个动作的按钮:
>> view layout [button "Hi" [print "Hello!"]]
显示按钮,打印一个字段的内容:
>> view layout [ in: field 200 button "Print" [print in/text] ]
Red语言中的一个Parse DSL示例:
red>> digit: charset "0123456789" red>> parse "hello 888 world" [ some [copy n some digit | skip] ] red>> n == "888"
由于想要解决Rebol语言的一些不足之处,Red语言应运而生。这些不足包括:
Red在解决以上大部分问题时都采用了创新设计:嵌入一个低层次的程序设计语言,它可以直接编译为本地代码,即Red/System。它采用Red语法(值仍为块的形式),但与C类似,语义级别更低。它是静态类型语言,并仅提供少数几种数据类型:integer!、float!、float32!、byte!、logic!、pointer!、struct!,以及function!。它算支持指针算术,这赋予了它近于C的力量。标准库也非常简约,所以纯Red/System编译后的代码尺寸非常小,下面这个程序:
Red/System [ Title: "Hello World app" ] print "Hello World!"
编译后通常小于10KB。
Red程序被编译成Red/System代码,并与Red标准库链接,而Red标准库则大部分采用Red/System,少部分用Red自身写就。该库目前未压缩体积约为200KB。我们的目标是在发行1.0版时,将其控制在500KB左右。
Red与Red/System紧密集成,有数种方法在Red语言中调用或内嵌Red/System代码。其中最有用的一种是“常规”函数类型,它允许定义一个Red函数,其函数体为纯Red/System代码。Red语言会为你自动完成传递参数和返回值的装箱/拆箱操作。有了Red/System的帮助,Red语言便可以解决从硬件到DSL的任何抽象层次,从而成为真正的首个全栈编程语言。
因此,Red依靠Red/System工具链来生成可执行文件。该编译器和链接器目前支持的目标平台包括:
除此之外,Red语言还依靠桥接技术,以访问像Java那样的虚拟机,尤其是提供对Android的支持。事实是,目前已可通过Red/Java桥,采用JNI技术来从Red程序远程控制Java和Android API,并获取事件返回。2014年晚些时候,我们还计划推出Red/Obj-C桥,以访问iOS和Cocoa API。
尽管后端和文件格式的组合可能性繁多,但确定选用何种目标平台的Red配置文件却短小精悍。这就使得交叉编译变得非常简单,例如:
从Linux或Mac平台上生成一个Windows平台的可执行文件:
$ red -t Windows hello.red
从Windows平台上生成一个Raspberry Pi平台的可执行文件:
$ red -t Linux-ARM hello.red
以下是关于这些目标平台的定义:
Windows [ OS: 'Windows format: 'PE target: 'IA-32 type: 'exe sub-system: 'GUI ] Linux-ARM [ OS: 'Linux format: 'ELF target: 'ARM type: 'exe base-address: 32768 ; 8000h dynamic-linker: "/lib/ld-linux.so.3" ]
Red语言的开发工作仍然任重道远。目前它的工具链部分使用Rebol2完成自举(指Red语言和Red/System编译器+链接器)。由于需支持JIT编译动态生成的Red/System代码,Red语言必须有自承载能力,所以所有工具链需要在1.0版之后用Red语言重写。新的工具链将能生成真正包含全部特征的Red语言,并同时为两种编译器提供优化层,生成更小更快的代码。目前,Red/System的性能实测下来大约比C慢4~6倍,这对于未经优化的本地代码而言已经不错了。
性能还会随着即将到来的并发支持而得到提升。今年晚些时候,Red语言会提供一个完全异步I/O接口,以及M:N线程模型(M个轻量级线程,分发到N个OS线程中去)。根据目前的计划,更高级别的抽象会使用Actor模型,这是为了实现简单而高效的共享状态同步。从用户的角度看,Actor也只是个一等的数据类型,和使用任何其他对象都差不多,很少或者根本没有额外的语法负担。然而,考虑到Go语言的goroutine模型的拥趸之多,我们仍在斟酌各种选项以决定使用Red语言的最佳模式。
Red语言是一个开源项目,采用BSD许可证。作为语言的作者,我全职工作于此业已三年。项目资金来自用户和支持者捐款。自2011年首次发布以来,得到的支持已然令我难以置信。开发者对于Red语言可提供的一切感到惊喜,尤其是那些了解Rebol语言能力的用户。Red语言对于他们中的大多数人,包括我自己,都是梦寐以求的工具。Red语言再次将乐趣带回了程序设计,将复杂性拒之门外,让程序员们再次感觉控制在握,就像那曾经经历的8位机时代。
由于Red语言将具备强大的Andr oid支持,又格外适合新入行的程序员,我们希望它能够被世界上最大的Andr oid市场——中国所接受。在中国,程序员数量巨大,而最有才华的Red语言贡献者中,就有一位是来中国上海的程序员谢晴天。
你可以从http://www.red-lang.org/p/download.html下载Red二进制文件。可执行文件只有半兆字节,包含了整个工具链,其中包括Red语言和Red/System编译器,一个带有可选控制台的解释器,还有目前支持30余种数据类型的整个标准库。你也可以尝试虽然老旧一些,但比较完整的Rebol2解释器,尤其是在测试GUI DSL时。
本文来自程序员杂志201403期,未经允许不得转载。