领域特定语言和通用编程语言

通用编程语言:有一系列足够完善的能力解决几乎所有计算机能解决的问题。
领域特定语言(DSL):专注在特定任务上,放弃与之无关的功能。如SQL,正则表达式。他们趋向于解决特定的问题,但对于整个应用程序,他们是无法构造的。

DSL趋向于声明式,而通用编程语言趋向于命令式。他们的区别在于:声明式语言会将想要的结果和执行细节留给解释他的引擎。通常只需要优化一次。而命令式语言要求每一个操作都被独立优化。通常声明式会更有效率一些。
而这就带来了一些缺点,比如SQL不能直接嵌入到其他语言中一起使用。单独的语言也需要单独的学习。

DSL风格的API和普通API

DSL的API往往会更加整洁
而且通过DSL比单独构造出来的API更具表现力和更适宜工作。

内部DSL

通过使用主要语言的特定方式,同时保留独立语法的DSL的主要优点。

DSL的结构

通常DSL和API没有明确定义的边界。判断的标准往往是,DSL有着特有的结构或者说文法。一般的API相互调用直接也没有维护上下文。调用时也没有特定语法结构:这种一般被称为命令查询API。与之不同的,DSL的方式会存在一个结构。在kotlin DSL中。结构通常采用嵌套的lambda表达式,或者链式方法创建的。
这种文法使我们能够将内部DSL称为一门语言。

带接收者的lambda

这是kotlin中的一个强大特性。
我们以buildString为例:

1
2
3
4
5
6
7
8
9
10
11
12
fun buildString(buiderStringAction: (StringBuilder) -> Unit): String {
val sb = StringBuilder()
buiderStringAction(sb)
return sb.toString()
}
fun main() {
val s = buildString {
it.append("Hello, ")
it.append("World!")
}
println(s)
}

这段代码很好理解。但是我们使用时,必须要通过显示的使用it(或者其他你显示定义的参数名)。才能完成传值。
如果我们想去掉it.前缀,用append代替it.append。要做到这一点,就需要使用带接受者的lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun buildString(buiderStringAction: StringBuilder.() -> Unit): String {
val sb = StringBuilder()
sb.buiderStringAction()
//下面这个方法在这任然可以使用
//buiderStringAction(sb)
return sb.toString()
}

fun main() {
val s = buildString {
append("Hello, ")
append("World!")
}
println(s)
}

这就是带接受者的lambda。它的append实际上就是this.append但是this可以省略。
这个样式是不是非常像扩展函数?通过扩展函数,不需要显示的修饰符就可以访问一个外部类的成员。如stringBuild使用append方法。实际上一个扩展函数类型描述了一个可以被当作扩展函数来调用的代码块。再次重申,这里的 builderAction 并不是StringBuilder 类的方法,它是一个函数类型的参数,但可以用调用扩展函数一样的语法调用它。

invoke约定

它的作用是可以把类当函数一样使用。
当你声明了operator invoke方法时,你直接调用类,作为方法,然后它会自动执行invoke中的函数体,如下:

1
2
3
4
5
6
7
8
9
10
11
class People{
operator fun invoke(name:String){
println(name)
}
}

fun main() {
People()("hzt")
val hzt = People()
hzt("SB")
}

以上语法都是正确的,这就是invoke约定。
这也可以解释为什么lambda函数可以直接在后头加个小括号直接调用。因为lambda函数都会转化为Function类。然后里面会有一个内置的方法invoke会存储你的lambda函数体。
而invoke用到的地方,大大优化了DSL,如下图:

infix声明

infix表示它可以使用中缀调用。