本期文章来自猎萝卜内部技术分享
DSL(Domain Specified Language)领域专用语言
常见的DSL
正则表达式
通过一些规定好的符号和组合规则,通过正则表达式引擎来实现字符串的匹配
HTML&CSS
虽然写的是类似XML 或者 .{} 一样的字符规则,但是最终都会被浏览器内核转变成Dom树,从而渲染到Webview上
SQL
虽然是一些诸如 create select insert 这种单词后面跟上参数,这样的语句实现了对数据库的增删改查一系列程序工作
DSL分类
内部 DSL(从一种宿主语言构建而来)
外部 DSL(从零开始构建的语言,需要实现语法分析器等)
通过Kotlin构建类型安全的DSL
例子:
这是在kotlin中完全合法的一段代码,并且可以正确运行出结果,得到的结果如下图
这就是我们自定义DSL构造器得出的结果。
首先我们回顾一些kotlin技术:
lambda与高阶函数
Kotlin 的 lambda 有个规约:如果 lambda 表达式是函数的最后一个实参,则可以放在括号外面,并且可以省略括号,这个规约是 Kotlin DSL 实现嵌套结构的本质原因。
传递lambda表达式作为参数:`,这个方法接收一个有receiver的lambda表达式,因为这样在block的内部就可以直接访问receiver的公共成员了,这一点也很重要。
扩展函数(扩展属性)
对于同样作为静态语言的 Kotlin 来说,扩展函数(扩展属性)是让他拥有类似于动态语言能力的法宝,即我们可以为任意对象动态的增加函数或属性。
比如,为 LocalDate 扩展一个函数::
与 JavaScript 这类动态语言不一样,Kotlin 实现原理是: 提供静态工具类,将接收对象(此例为 String )做为参数传递进来,以下为该扩展函数编译成 Java 的代码
在Kotlin语言中,类不再是语言的最小单位。我们既可以单独声明一个全局函数,也可以声明全局变量。因此,你可以认为toLong是一个函数整体,这个函数的接收者可以是任意对象。
而对于Java,Java语言中所有的行为都必须在类体中完成。具体到某个函数或某一个变量始终属于某一个类实例。换而言之,其Receiver是固定的,也就没有了所谓Receiver的概念。
一元运算符
开始分析
实现原理
首先看
这个代码块的实质是一个函数调用
这个函数接受一个名为的参数,该参数本身就是一个函数。 该函数的类型是,它是一个带接收者的函数类型。 这意味着我们需要向函数传递一个 HTML 类型的实例(接收者), 并且我们可以在函数内部调用该实例的成员。 该接收者可以通过this关键字访问:
(和是的成员函数。)
现在,像往常一样,this可以省略掉了,我们得到的东西看起来已经非常像一个构建器了:
它创建了一个的新实例,然后通过调用作为参数传入的函数来初始化它 (在我们的示例中,归结为在HTML实例上调用和),然后返回此实例。 这正是构建器所应做的。
类中的和函数的定义与类似。 唯一的区别是,它们将构建的实例添加到包含实例的集合中:
实际上这两个函数做同样的事情,所以我们可以有一个泛型版本,:
所以,现在我们的函数很简单:
并且我们可以使用它们来构建和标签。
这里要讨论的另一件事是如何向标签体中添加文本。在上例中我们这样写到:
所以基本上,我们只是把一个字符串放进一个标签体内部,但在它前面有一个小的, 所以它是一个函数调用,调用一个前缀操作。 该操作实际上是由一个扩展函数定义的,该函数是抽象类(的父类)的成员:
所以,在这里前缀所做的事情是把一个字符串包装到一个实例中,并将其添加到集合中, 以使其成为标签树的一个适当的部分。
作用域控制
由于内部的作用域默认可以获得外部的隐式接收器
我们可以使用来注释一个注解
注释类被称为一个DSL标记,它被注解注释。
一般规则:
如果隐式接收器用@HtmlTagMarker相应的DSL标记注释标记,则它可以属于DSL
同一DSL的两个隐式接收器在同一范围内不可访问
就近原则
其他可用的接收器可以照常解析,但如果得到的解析调用绑定到这样的接收器,则编译错误
标记规则:隐式接收器被视为被注释,需要满足下面的条件:
它的类型是被标记,或
它的类型分类器被标记
或其任何超类/超接口
补充说明
无论是否被标记,都可以访问接收器
完整代码
应用
kotlin官方html构造器:kotlinx.html
kotlin的javaFX框架:TornadoFX
安卓布局框架:anko
kotlin服务端框架:ktor
参考文档
类型安全的构建器
Scope control for implicit receivers
Kotlin之美——DSL篇
领取专属 10元无门槛券
私享最新 技术干货