Fluent API -- 现代DSL
论文导读系列(I)
Formal Language Recognition with the Java Type Checker[2016]
为什么Fluent API?
我们为什么需要DSL(领域特化语言)?
从个人观点来看 是为了更精简地表达
人们喜欢函数式语言的原因也是一样的
在函数式编程里杜绝所有的状态变化
是为了从交互式编程(Imperative programming)转向指令式编程(Declarative programming)
从而实现更精简的表达
可是世界上无数的系统 无数的领域
难道每个都实现自己的一套语言吗?
我觉得这是难以接受的
虽然现在想实现一个语言已经很方便了
只要提供一个BNF就会有工具帮我们实现自动机
可是不可否认这个过程门槛仍然是很高的
Fluent API是什么?
以防有人不知道我在这里简单描述一下
它就是能够串起来调用的一些函数,比如a().b().c()...
但是这里需要明确一点,Java的StringBuilder不属于Fluent API
因为它返回的永远是同一个对象,意味着所有的调用都发生在这个对象上
而Fluent API每个函数的返回类型都是任意的
StringBuilder这种调用方式被称为method chaining
Fluent API != Method Chaining
Fluent API将变量声明给省略了
这就是它的威力所在
极大地提高了语言的表达能力(精简性)
函数式语言不就是想要这个效果么?
也许聪明的你已经意识到
Fluent API是不是可以代替DSL呢?
事实上Fluent API就是DSL
但它是被嵌到宿主语言(实现语言)上的DSL
所以被称为EDSL(Embed DSL)
Fluent API就是DSL
似乎人们最近几年才理解到这一点
因为它拥有跟DSL同等的表达力
还能够用宿主语言的特性
它的性质决定了Fluent API今后一定会大火
为什么要类型检查?
原因是:
- 从经验上判断,对Fluent API进行类型检查是可行的
- 既然类型检查可行,我们就希望在编译时检查出问题减少运行时出现错误的几率
怎么写一个支持类型检查的Fluent API?
这里引入一个Box系统作为例子:
盒子支持两种操作,close & open
这也是最简单的一个DSL
现在我们来给它编码以支持类型检查
interface Box{
open(): Box;
close(): Box;
}
class TheBox implements Box{
open(){
return this;
}
close(){
return this;
}
}
new TheBox().close().open().close()
注意这里的open和close返回的是一个Box,以支持Fluent API
这样算是实现了类型检查吗?
当然 至少保证了Box类型不能调用explode函数
然后问题就暴露出来了:
众所周知,一个打开的盒子不能再打开,一个关闭的盒子不能再关闭
也就是说: box.open().open()
or box.close().open()
都是不合法的
怎么写一个语义正确的支持类型检查的Fluent API?
Box类型其实应该细分为OpenBox和CloseBox
上个自己画的图, 这是一个自动机(伪), 由图可知,函数close, open,实际上是partial function:
这种内部实际上拥有复杂状态的类型叫做stated-type
与此相对的举个例子就是Java的StringBuilder,就不细说了
下面是实现:
class CloseBox implements Box{
open(){
return new OpenBox();
}
close(): never{
throw "No I should not call this method"
}
}
class OpenBox implements Box{
open(): never{
throw "No I should not call this method"
}
close(): CloseBox{
return new CloseBox();
}
}
new OpenBox().open() // I want the type checker to report an error.
当我调用OpenBox的open方法的时候, 最理想的情况是编译器能报一个错误:
但是我发现typescript好像没办法做到这件事情 让我十分苦恼
我在stackoverflow上问了这个问题Can typescript define uncallable method in a class?
论文中用的是java,作者用了一点trick去实现了这件事情但是可读性贼差
我认为语言完全应该实现这个feature
比如我返回never类型那么类型系统就不应该让我调用这个方法
我也在typescript的github项目上提了个issue
这里我直接throw一个error,让我们的脑内编译器抛出一个错误吧
这就是一个语义正确的支持类型检查的Fluent API的实现
自动化Fluent API生成
上下文无关文法是可以描述stated-type的
比如我们给Box系统提供一个BNF, 他会是这个样子:
// epsilon是希腊字母那个, 代表空
Box := open close Box | epsilon
在Box这个语言里 open open
这样一个语句是不合法的
没办法通过编译
换个角度, stated-type实际上是一个语义问题
我们能从BNF手动写出一个 语义正确的提供类型检查的Fluent API
然后我们思考,从BNF中识别出stated-type的规律是什么?
如果发现了这个规律,不单只手写API的效率大幅提高,不用动脑子
我们甚至能够 自动生成Fluent API
然后就是论文的精髓所在了
识别语义?这不就是下推自动机的工作么?
下推自动机有一个栈
这个栈的本质其实就是语义
只有在当前栈(语义)状态下,才能够调用sigma方法
这就是: 通过对下推自动机的模仿(mimic)实现语义类型系统
然后按部就班将自动机翻译成Fluent API就好了
这就是自动化Fluent API的生成逻辑
总结
Fluent API🐂🍺