稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

Understanding Kotlin Based DSL

DSL Domain Specific Langauge是解决特定领域内问题的编程语言,它的特点通常是简洁,虽不通用,但可读性强,呈描述式和声明式,比较典型的例子就是SQL语句和正则表达式,以及一些文本处理工具如Sed和AWK。与之对应的就是通用编程语言GPL General Purpose Language,能解决几乎所有的计算机问题,没有为特定领域进行定制,但语句可读性远不及DSL,它们的语句一般都是命令式的,如熟悉的C/C++, Java和Python等。

GPL中的DSL痛点

DSL的优点是声明式的,描述式的,并不在意具体细节的实现,比如像SQL语句,我只关心我想要什么,具体如何实现的是底下工具的事情:

1
2
3
select (id, name, address)
    where name = "John" and age <= 10
    from classlist

但,当在GPL,通用编程语言中解决DSL问题时,就会很蛋疼,在任何一个编程语言中实现一个SQL查询 的接口都会相当的笨拙和难用,要么需要创建很多个对象,要么需要传递一大堆令人费解的参数:

1
Cursor cursor = query(uri, projects, where, whereArgs, sortby);

可读性非常的差,假如能写成DSL那样,可读性会大大的增强。

DSL式的API

在Kotlin中,可以写成这样:

1
2
3
4
5
6
7
query {
    from "classlist"
    where {
        "name" eq "John"
        "age" lessEq 10
    }
}

可读性大大加强,这样的API使用起来也会如丝般顺滑,因为它符合DSL:描述性的,声明式的,不关心具体实现细节,只关注想要什么。

背后原理

Kotlin借助尾部lambda,infix函数,扩展函数。整体来就其实是一个函数调用,因为Kotlin函数最后一个参数如果是lambda时可以写在函数调用之外,所以,一层一层的花括号,其实就是函数调用。

再借助扩展函数和infix函数,可以把另外一些函数调用写成DSL式,比如像二进制按位或运算a or b等同于a.or(b),这也是一个函数调用,但可读性大大加强。可以像写不作文一样的来写代码。

总之,见到DSL式的语句时不用惊慌,它是合法的Kotlin函数调用,把它理解成为函数调用就可以了。

合理使用

虽然DSL式的API能大大提高可读性,像真正的DSL一样去使用,但也要注意合理使用,不能滥用。我们要正向的解决问题,当遇到特定领域(Domain)的问题时,就可以定义和实现出一套符合DSL的API。也就是说我们要利用Kotlin语言提供的能力来实现DSL式的API去解决特定领域的问题。就像Android的布局,就可以定义出一套DSL式的API,这就是当时比较火的anko(现已废弃了)和现在的Compose,以及像涉及HTML,SQL的等特定领域问题时。

但不能反过来,为了写成DSL式,而去把所有的API都搞成DSL那样,比如把一些常规的操作像网络,像文件操作等都搞成那样,那就纯属滥用了。

参考资料

Comments