F.1 Clojure语言的DSL相关特性
Clojure是一种植根于JVM的函数式编程语言,以通用编程语言为定位。它属于动态类型的语言,具有类型推断的特性,还可根据需要向编译器提供类型提示(type hint)以提高效率。Clojure是一种Lisp方言,直接编译成JVM字节码执行。Clojure语言具有同像(homoiconic)的特征,且内建了丰富而强大的并发控制结构。
除了表F-1所列的项目,Clojure还有很多值得了解的语言特性,包括并发和状态管理方面的特性、各种缓求值序列(lazy sequence)、序列推导(sequence-comprehension)和循环,以及众多高级数据结构。详情可以查阅第F.2节文献[1]。
表F-1 Clojure语言特性汇总
函数式——围绕函数来组织DSL | |
---|---|
我们的整套DSL全部由函数构成 函数是Clojure首要的语言构造。Clojure的函数支持高阶函数和闭包特性,也支持匿名函数 Clojure虽然建立在Java之上,但它的函数式特征完全占据主导地位。尽管Clojure允许我们下降到Java层面去调用对象语义,但就语言习惯来说,Clojure是函数式的 |
前缀语法现在已经不会让人感到陌生 注意: Clojure具有“同像(homoiconic)”的性质。函数调用操作本身也是一个列表,以函数的名字作为列表的第一个元素。
even? 是一个Clojure函数,其作用是当输入为偶数时返回true 。我们在上面例子中将even? 函数作为参数之一传递给filter ,filter 会用它来逐一筛选输入列表的每一个元素。
我们还可以把匿名函数传递给 filter
|
函数式——函数定义 | |
我们通过defn 宏来定义函数。如右边的例子所示,其语法非常纯粹。函数本身也是数据,只不过开头的位置添加了一个有语义意义的符号,这是Clojure(以及其他Lisp变体)最为引人注目的特点
|
函数定义以 defn 开头。然后是文档字符串(docstring),也就是函数的说明文档。接着是放在一个vector里的参数列表。最后是函数体
我们可以根据需要插入元数据,元数据以^ 前缀开头。例中的^String 标明了函数的返回类型
这段函数定义本身也是一个Clojure列表结构,定义的每一个构成部分都是列表中的元素
|
抽象设计 | |
Clojure方式 | 传统OO方式 |
- 字段公开; - 对象不可变; - 由多路方法(multimethod)和协议(protocol)实现多态; - 不存在实现继承 | - 数据都作为私有成员隐藏在类中; - 对象是可变的; - 由层级化的继承关系形成多态,继承关系可以是接口继承,也可以是实现继承; - 允许实现继承 |
序列 | |
Clojure语言的任何一种集合数据类型都是一个序列。我们可以一视同仁地对待所有种类的序列,通过相同的API来操作它们。另外,所有的Java集合类型都可以当做Clojure序列来对待,如右边的例子所示 |
在这段代码中: - 第一个例子在一个 List 上调用first ;
- 第二个例子在一个Vector 上调用rest ;
- 第三个例子在一个Map 上调用first
|
序列同时也是函数 | |
Clojure把所有的序列类型都当成函数看待。这种观点源自序列的数学定义
Clojure的各种序列都可以从数学的角度去表述,下面是几个例子:
- Vector 是它的位置的函数;
- Map 是它的键的函数;
- Set 是它的成员关系的函数
|
|
创建序列 | |
Clojure提供了一系列的序列创建函数。其中很大一部分函数返回的序列是缓求值序列,因此可以用于创建无限长的序列 |
|
对序列进行筛选 | |
Clojure为序列的筛选操作提供了若干组合子。我们应该优先使用这些组合子,尽量避免程序中出现显式的递归 |
|
对序列进行变换 | |
Clojure提供了大量对已存在序列进行变换操作的组合子。它们以一个序列作为输入,输出变换后产生另一个序列或值 |
|
数据结构的持久性和不可变性 | |
Clojure语言中所有的数据结构都具有不可变(immutable)和持久(persistent)的性质。也就是说,即使一个对象发生了改变,我们仍然可以访问它的所有历史版本。而且Clojure的实现方式并不需要额外占用大量的存储空间 |
|
宏 | |
Clojure进行DSL设计的秘诀是宏。宏是一种在宏展开阶段被展开替换为有效Clojure语言成分的程序结构。 宏是我们为DSL量身定做各种语法结构的利器 |
我们在这个宏里定义了一种类似于 if 和while 的控制结构。它在使用的时候和一般的Clojure语言成分并没有什么两样
|