zoukankan      html  css  js  c++  java
  • 七周七语言之用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 方法;curlyBracketssquareBrackets 为 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 时,那么可以大胆发挥自己的想象力,在它的基础上构建一门属于自己的语言。

  • 相关阅读:
    26 playbooke(二)
    25 playbook(一)
    23 ansible模块2
    23 ansible模块(一)
    22 Ansible相关工具、ansible、ansible-vault、ansible-console、ansible-galaxy
    21 Ansible安装与主机清单的编写
    Druid.jar包
    Servlet--request
    mysql-myini配置
    global,tornado的write_error,tornado的中间件的写法
  • 原文地址:https://www.cnblogs.com/lcomplete/p/sevenlang-io.html
Copyright © 2011-2022 走看看