七周七语言之用Io编写领域特定语言
如果你想获得更好的阅读体验,可以前往我在 github 上的博客进行阅读,http://lcomplete.github.io/blog/2013/06/05/sevenlang-io/。
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 中来创造这种语法糖。
OperatorTable addAssignOperator(":", "atPutNumber")
Map atPutNumber := method(
  self atPut(
    call evalArgAt(0) asMutable removePrefix("\"") removeSuffix("\""),
    call evalArgAt(1)
  )
)
curlyBrackets := method(
  data := Map clone
  call message arguments foreach(arg,
    data doMessage(arg))
  data
)
squareBrackets := method(
    arr := list()
    call message arguments foreach(arg,
        arr push(call sender doMessage(arg))
    )
    arr
)
doFile("dict_sample.io")
先简单说明一下上面这段代码,OperatorTable 中保存了可用的操作符号表,我们可以使用 addAssignOperator 方法向其中添加用于赋值的操作符号,并将其映射到第二个参数所指定的方法中,第一行代码的意思为将 : 符号映射到 atPutNumber 方法中,当在代码中碰到 : 符号时,将调用atPutNumber 方法;curlyBrackets、squareBrackets 为 io 中的特殊变量,将方法赋值给它们相当于改变 {} 和 [] 符号的行为,在我们的代码中,指定了当碰到 { 符号时初始化 Map 对象,接着循环处理参数,这里的参数是用 , 符号分段的,data doMessage 方法相当于以data为上下文执行参数中的代码,参数代码中若碰到 : 符号则会调用 atPutNumber 方法,将符号左边的参数当作键,右边的参数当作值存入data中,最后返回data。
注意,最后一行的 doFile 方法是执行另一文件中的代码的意思,为什么这个例子要分成两个文件呢?主要是因为虽然我们在操作符号表里面添加了一个符号,但是在这个文件的执行上下文中,是不会起效果的,只有调用 doString 或 doFile 才会起效,所以将创建字典的代码放在了下面这个文件中。
dict := {
  "name": "lcomplete",
  "array": [1, "str"],
  "nested": {
    "hello" : "io"
  }
}
很简洁吧,而且还支持嵌套定义哦。
通过第一个例子,相信各位已经能看出 io 在创造内部dsl上的灵活性了,接下来我们看一个更强大的例子,使用 io 让编写html变的更有禅意,何谓禅意,其实是受到 Sublime 上的 ZenCode 插件的启发,使用 ZenCode 语法可以快速的编写 html 片段。这次我们先看一下使用 dsl 的方式。
zencode(Html html>head+body>div!content$id@class*3)
不难发现,这个语法已经跟宿主语言几乎没有任何关系,是一门彻底的内部dsl,上面这段代码生成的 html 如下所示:
<html>
<head>
</head>
<body>
<div id="id" class="class">
content
</div>
<div id="id" class="class">
content
</div>
<div id="id" class="class">
content
</div>
</body>
</html>
很强大吧,一行代码就可以生成出这么多的 html,现在我们再看看这个 dsl 是如何定义出来的。
forward := method(
call message name
) root := nil Html := Object clone do(
OperatorTable addOperator("@", 15)
OperatorTable addOperator("$", 15)
OperatorTable addOperator(">", 15)
OperatorTable addOperator("+", 15)
OperatorTable addOperator("*", 15)
OperatorTable addOperator("!", 15) @ := method(classname,
attrs append(" class=\"#{classname}\"" interpolate)
self
) $ := method(id,
attrs append(" id=\"#{id}\"" interpolate)
self
) > := method(tag,
html := Html clone
html tag := tag
html parent := self
self childs append(html)
html
) + := method(tag,
html := nil
if(self parent,
html = Html clone
html tag := tag
self parent childs append(html)
)
html
) * := method(count,
self count := count asNumber
self
) ! := method(content,
self content := content
self
) forward := method(
name := call message name
html := Html clone
html tag := name
root = html
html
) init := method(
self childs := list()
self attrs := list()
self count := 1
self parent := nil
self content := nil
)
) Html flush := method(
root render("")
) Html render := method(indent,
self count repeat(
writeln(indent,"<",self tag,self attrs join(""),">")
childIndent := indent .. " "
if(self content,
writeln(childIndent,self content)
)
self childs foreach(index,arg,
arg render(childIndent)
)
writeln(indent,"</",self tag,">")
)
) zencode := method(html,
html flush
) doFile("enhance_html.io")
有几处需要说明一下,forward 是一个特殊方法,相当于 ruby 中的 method_missing ,当调用的方法不存在时,系统将会把调用转向到 forward 方法,这就给了我们很大的灵活性,我们无需定义 html、head 等方法,这些未定义的方法会自动转向到 forward 中处理;注意,一共有两个 forward 方法,分别应用在 Object 和 Html 对象上,只有 Html html 会返回 Html 对象,其他的未定义方法会转向到 Object 的 forward 方法上,这里直接将方法名作为字符串返回,新添加的 @、$ 等操作符,将会把接受到的字符串进行处理,最后再返回一个 Html 对象以进行链式调用。
总结
在 Io 中可以轻松地定义操作符、实现动态方法或更改所有对象的行为,这些能力是强大的,也是危险的,使用之前需要仔细衡量。当然,如果只是用它来做某一件事情,比如用来编写上面的 ZenCode 时,那么可以大胆发挥自己的想象力,在它的基础上构建一门属于自己的语言。
七周七语言之用Io编写领域特定语言的更多相关文章
- 七周七语言之用ruby做点什么
		
如果你想获得更好的阅读体验,可以前往我在 github 上的博客进行阅读,http://lcomplete.github.io/blog/2013/05/25/sevenlang-ruby/. 每学一 ...
 - Seven xxx  in Seven Weeks ebooks |  七周七 xxx 系列图书  电子书| share  分享 | free of charge  免费!
		
Seven xxx in Seven Weeks ebooks | 七周七 xxx 系列图书 电子书| share 分享 | free of charge 免费! Seven Languag ...
 - 正则表达式与领域特定语言(DSL)
		
如何设计一门语言(十)——正则表达式与领域特定语言(DSL) 几个月前就一直有博友关心DSL的问题,于是我想一想,我在gac.codeplex.com里面也创建了一些DSL,于是今天就来说一说这个事情 ...
 - 在Visual Studio 2012中使用VMSDK开发领域特定语言(二)
		
本文为<在Visual Studio 2012中使用VMSDK开发领域特定语言>专题文章的第二部分,在这部分内容中,将以实际应用为例,介绍开发DSL的主要步骤,包括设计.定制.调试.发布以 ...
 - 在Visual Studio 2012中使用VMSDK开发领域特定语言(一)
		
前言 本专题主要介绍在Visual Studio 2012中使用Visualization & Modeling SDK进行领域特定语言(DSL)的开发,包括两个部分的内容.在第一部分中,将对 ...
 - 在Visual Studio 2012中使用VMSDK开发领域特定语言1
		
在Visual Studio 2012中使用VMSDK开发领域特定语言(一) 前言 本专题主要介绍在Visual Studio 2012中使用Visualization & Modelin ...
 - [综述]领域特定语言(Domain-Specific Language)的概念和意义
		
领域特定语言(Domain Specific Language, DSL)是一种为解决特定领域问题而对某个特定领域操作和概念进行抽象的语言.领域特定语言只是针对某个特定的领域,这点与通用编程语言(Ge ...
 - 七周七语言——Prolog(二)
		
1 递归 首先来看一个知识库: father(zeb,john_boy_sr). father(john_boy_sr,john_boy_jr). ancestor(X,Y):-father(X,Y ...
 - 七周七语言之使用prolog解决爱因斯坦斑马难题
		
如果你想获得更好的阅读体验,可以前往我在 github 上的博客进行阅读,http://lcomplete.github.io/blog/2013/06/28/sevenlang-prolog/. 目 ...
 
随机推荐
- PHP分页函数 学习笔记
			
function smarty_function_assign_debug_info($params, &$smarty){ $assigned_vars = $smarty->_tpl ...
 - linux——Shell编程基础
			
1. shell 脚本的执行方式 1.1 直接绝对路径执行 1.2 相对路径执行 首先进入到shell脚本所造的目录 PS:用./执行要增加x权限.用bash执行可以不增加x权限 1.3 在当前she ...
 - PMP考试通过
			
经过3个月的努力,终于在10月8号,过完国庆假期,得知考试通过.虽然没有得到5A,只有4A,心也算落下了.备考的过程中,通过学习小组讨论,互相交流,辅导. 自己也对学习的知识加深印象.总结一下整个学习 ...
 - Oracle入门第六天(下)——高级子查询
			
一.概述 主要内容: 二.子查询介绍 1.简单子查询(WHERE子查询) SELECT last_name FROM employees WHERE salary > (SELECT salar ...
 - elasticsearch 安装问题
			
Elasticsearch5.0 安装问题集锦 elasticsearch 5.0 安装过程中遇到了一些问题,通过查找资料几乎都解决掉了,这里简单记录一下 ,供以后查阅参考,也希望可以帮助遇到同样问题 ...
 - CF833D Red-Black Cobweb
			
题面 题解 点分治大火题... 设白边数量为$a$,黑边为$b$,则$2min(a,b)\geq max(a,b)$ 即$2a\geq b\;\&\&2b\geq a$ 考虑点分治时如 ...
 - 最优布线问题(wire.cpp)
			
最优布线问题(wire.cpp) [问题描述] 学校有n台计算机,为了方便数据传输,现要将它们用数据线连接起来.两台计算机被连接是指它们间有数据线连接.由于计算机所处的位置不同,因此不同的两台计算机的 ...
 - Zabbix学习之路(二)之添加主机监控及自定义item监控
			
1.zabbix_get命令详解 安装zabbix-get命令 [root@linux-node1 ~]# yum install -y zabbix_get 参数说明: -s --host: 指定客 ...
 - hdu2065"红色病毒"问题(指数母函数+快速幂取模)
			
"红色病毒"问题 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Other ...
 - Selenium2+python自动化-iframe
			
前言 本篇详细讲解iframe的相关切换操作. 一.frame和iframe区别 Frame与Iframe两者可以实现的功能基本相同,不过Iframe比Frame具有更多的灵活性. frame是整个页 ...