作用域控制:@DslMarker (since 1.1)
当我们在使用DSL(特定领域语言)时,一个出现在脑海的问题可能就是在当前上下文中太多的函数可以被访问。我们可以调用lambda表达式中所有可用的方法,并且得到一个前后矛盾的结果,就像head标签与head标签的嵌套:
html {
head {
head {} // should be forbidden
}
// ...
}
在这样的例子中,只有是最近一个接收器的成员(this@head)才可以使用;head()是外部接收器this@html的一个成员,因此调用它是非法的。其实意思就是hear方法属于html,而不是head,因此第二个head是非法的。
为了处理这个问题,Kotlin 1.1引入了一个控制接收器范围的机制。
为了让编译器开始控制作用域,我们只需要使用同一个注解来标记在DSL中使用的接收器类型。比如,对HTML来说,我们定义一个叫做@HTMLTagMarker的注解:
@DslMarker
annotation class HtmlTagMarker
如果一个注解类被@DslMarker标记,那么它被称之为DSL marker。
在Kotlin中,所有的标签类都是继承自相同的超类Tag。只使用@HtmlTagMarker标记超类就可以了,所有子类都会被Kotlin编译器处理成如下的形式:
@HtmlTagMarker
abstract class Tag(val name: String) { ... }
我们不需要将HTML和Head使用@HtmlTagMarker标记,因为它们的超类已经标记过了:
class HTML() : Tag("html") { ... }
class Head() : Tag("head") { ... }
当我们添加这个注解之后,Kotlin编译器就会知道相应标签的控制域。
html {
head {
head { } // error: a member of outer receiver
}
// ...
}
需要注意的是现在可以访问外部接收器的成员,但是我们显示的指定接收器:
html {
head {
[email protected] { } // possible
}
// ...
}