Io 语言是在2002年创造出来的,虽然距今已经有11个年头了,但是对于一门编程语言来说,它还只能算是一门年轻的语言。Io 并不是主流编程语言,没有什么名气,就连名字取的也并不适合推广,io 有太多其他的含义了,用google直接搜索 io 的话,很难找到关于这门编程语言的资料,英文推荐搜索“io language”,中文则建议搜索“io 编程语言”。
Io 的优缺点
Io 是个小众语言,文档不足,这是其中的一个缺点,但是这门语言吸收了许多语言的特点,具有很高的学习价值。Io 的设计借鉴了 SmallTalk、Self、NewtonScript、Act1、LISP和 Lua 的一些特点,它是一门基于原型式设计的编程语言,具有面向对象特性,语法概念极少,它没有关键字,所以能够在很短的时间内熟悉语法,Io 还有出色的并发库,利用它能够以简单直观的方式编写并发程序。
Io 有很少的语法规则和语言特性,这似乎在某种程度上束缚了它的能力,但是使用 Io 可以很容易地扩展这门语言自身的语法,我们可以自定义运算符或改变运算符的行为,也可以使用元编程在任何时候改变任何对象的行为。正是这些高扩展性和可定制性使得 Io 在创造领域特定语言(Domain Specific Language,简称DSL)方面具有非常强大的能力,我们可以利用它创造出自己喜欢的语法,编写出能够编写程序的程序,学习 Io 颇具启发性,怎样利用 Io 打造出能够提高编程效率的 DSL 更是一个值得深入研究的课题。
什么是领域特定语言?
Martin Flower 在《领域特定语言》一书中给出的定义是:针对某一特定语言,具有受限表达性的一种计算机程序设计语言。DSL 不是图灵完备的,所以它的表达能力有限;由于它是领域特定的,它的出现往往是为了消除业务领域的复杂性,所以 DSL 一般是整洁的、易于使用且接近自然语言的。我们平时在编码的过程中其实会接触到很多领域特定语言,比如 CSS、HTML、XML、JSON、正则表达式、XAML,他们都是针对某一领域的,“求专不求全”,CSS用于定义Web页面样式;HTML用于定义Web界面元素;XML、JSON用于描述数据,这些数据可以作为纯数据,也可作为某种行为的配置。
DSL 从实现角度分为内部DSL和外部DSL,用XML来描述某种行为就是一种常见的外部DSL的形式,利用编程语言自带的语法结构定义出来的 DSL 称为内部DSL,比如时下流行的流畅接口(Fluent Interface)编码方式就是一种内部DSL。上面说到 Io 编程语言可以自定义运算符且具有元编程特性,因此 Io 可以方便的创造出内部DSL,下面我们就通过几个示例来看一看 Io 在这方面的能力。
使用 Io 创造内部DSL
先看一个比较简单的例子,可以说在所有的编程语言中都有列表和字典(一些语言中也称为映射)这两种数据结构,在 io 中也不例外,io 中创建列表对象可以使用 list 函数,比如 foo := list(1,2) ,这很方便,然而创建一个字典的写法却比较繁琐,你需要 clone 一个 Map 对象,然后依次调用 atPut 方法向其中添加键值对,作为一个常用的数据结构,这样写不仅会影响效率而且会使代码变的臃肿,纵观其他编程语言,大部分都有相应的字典初始化语法糖,常用的写法为 {“key”:”value”} 结构,下面我们就在 io 中来创造这种语法糖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
先简单说明一下上面这段代码,OperatorTable 中保存了可用的操作符号表,我们可以使用 addAssignOperator 方法向其中添加用于赋值的操作符号,并将其映射到第二个参数所指定的方法中,第一行代码的意思为将 : 符号映射到 atPutNumber 方法中,当在代码中碰到 : 符号时,将调用 atPutNumber 方法;curlyBrackets、squareBrackets 为 io 中的特殊变量,将方法赋值给它们相当于改变 {} 和 [] 符号的行为,在我们的代码中,指定了当碰到 { 符号时初始化 Map 对象,接着循环处理参数,这里的参数是用 , 符号分段的,data doMessage 方法相当于以data为上下文执行参数中的代码,参数代码中若碰到 : 符号则会调用 atPutNumber 方法,将符号左边的参数当作键,右边的参数当作值存入data中,最后返回data。
注意,最后一行的 doFile 方法是执行另一文件中的代码的意思,为什么这个例子要分成两个文件呢?主要是因为虽然我们在操作符号表里面添加了一个符号,但是在这个文件的执行上下文中,是不会起效果的,只有调用 doString 或 doFile 才会起效,所以将创建字典的代码放在了下面这个文件中。
1 2 3 4 5 6 7 |
|
很简洁吧,而且还支持嵌套定义哦。
通过第一个例子,相信各位已经能看出 io 在创造内部dsl上的灵活性了,接下来我们看一个更强大的例子,使用 io 让编写html变的更有禅意,何谓禅意,其实是受到 Sublime 上的 ZenCode 插件的启发,使用 ZenCode 语法可以快速的编写 html 片段。这次我们先看一下使用 dsl 的方式。
1
|
|
不难发现,这个语法已经跟宿主语言几乎没有任何关系,是一门彻底的内部dsl,上面这段代码生成的 html 如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
很强大吧,一行代码就可以生成出这么多的 html,现在我们再看看这个 dsl 是如何定义出来的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
|
有几处需要说明一下,forward 是一个特殊方法,相当于 ruby 中的 method_missing ,当调用的方法不存在时,系统将会把调用转向到 forward 方法,这就给了我们很大的灵活性,我们无需定义 html、head 等方法,这些未定义的方法会自动转向到 forward 中处理;注意,一共有两个 forward 方法,分别应用在 Object 和 Html 对象上,只有 Html html 会返回 Html 对象,其他的未定义方法会转向到 Object 的 forward 方法上,这里直接将方法名作为字符串返回,新添加的 @、$ 等操作符,将会把接受到的字符串进行处理,最后再返回一个 Html 对象以进行链式调用。
总结
在 Io 中可以轻松地定义操作符、实现动态方法或更改所有对象的行为,这些能力是强大的,也是危险的,使用之前需要仔细衡量。当然,如果只是用它来做某一件事情,比如用来编写上面的 ZenCode 时,那么可以大胆发挥自己的想象力,在它的基础上构建一门属于自己的语言。