As we start to nest function calls, we may end up with multiple receiver contexts available to us. This can start to get confusing or even incorrect. For example, in the HTML example, creating a HEAD element inside another HEAD is probably not the desired behavior.
To help avoid this situation, we can make use of the DslMarker annotations. The use of these annotations can tell the Kotlin compiler when to limit the scope of a given function so that outer scopes are no longer available.
To create a DslMarker, we can create a custom annotation, as shown in the HTML DSL example:
@DslMarker
annotation class HtmlTagMarker
By adding that annotation to the parent class, Tag, we can indicate which receivers are available within a given scope:
@HtmlTagMarker
abstract class Tag(val name: String) : Element {
...
}
Once we've applied DslMarker, the following code becomes invalid:
html {
head {
// error: can't be called in this context by implicit receiver
head { }
}
}
This extra layer of control can help ensure that the methods that are available by default in any scope within the DSL are the most relevant to the current context, thereby making the DSL easier to work with. Armed with this knowledge, we can create our first DSL and understand it with the help of an example.