DSL scope control with @DslMarker in Kotlin - Time & Space Complexity
When using Kotlin's DSL scope control with @DslMarker, it's important to understand how the program runs as the input grows.
We want to know how the number of operations changes when we build nested DSL structures.
Analyze the time complexity of the following Kotlin DSL builder code.
@DslMarker
annotation class HtmlTagMarker
@HtmlTagMarker
class Html {
private val children = mutableListOf()
fun head(init: Head.() -> Unit) { children.add(Head().apply(init)) }
fun body(init: Body.() -> Unit) { children.add(Body().apply(init)) }
}
@HtmlTagMarker
class Head {
fun title(text: String) {}
}
@HtmlTagMarker
class Body {
fun p(text: String) {}
}
fun html(init: Html.() -> Unit): Html = Html().apply(init)
This code defines a simple HTML DSL with scope control using @DslMarker to avoid confusion in nested blocks.
Look for loops or repeated calls inside the DSL builders.
- Primary operation: Each nested block calls
apply(init)which runs the lambda passed to it. - How many times: The number of nested blocks determines how many times these lambdas run.
As you add more nested tags inside the DSL, each block runs its lambda once.
| Input Size (nested tags) | Approx. Operations |
|---|---|
| 10 | About 10 lambdas executed |
| 100 | About 100 lambdas executed |
| 1000 | About 1000 lambdas executed |
Pattern observation: The work grows roughly in direct proportion to the number of nested blocks you create.
Time Complexity: O(n)
This means the time to build the DSL grows linearly with the number of nested blocks.
[X] Wrong: "Using @DslMarker adds extra loops or slows down the DSL building significantly."
[OK] Correct: @DslMarker only affects how the compiler checks scope, it does not add runtime loops or extra operations.
Understanding how DSL scope control affects execution helps you write clear and efficient Kotlin DSLs, a useful skill in many Kotlin projects.
"What if we added recursion inside the DSL builders? How would the time complexity change?"
Practice
@DslMarker annotation in Kotlin DSLs?Solution
Step 1: Understand DSL nesting problem
In nested DSL blocks, functions from outer scopes can be accidentally called inside inner blocks, causing confusion.Step 2: Role of @DslMarker
@DslMarker annotation marks DSL builder classes to restrict visibility of outer scope functions inside nested DSL blocks, preventing accidental calls.Final Answer:
To limit function visibility and prevent accidental calls to outer DSL scopes inside nested blocks. -> Option AQuick Check:
@DslMarker controls scope visibility = A [OK]
- Thinking @DslMarker generates code automatically
- Confusing @DslMarker with deprecation annotations
- Assuming @DslMarker enables inline functions
Solution
Step 1: Recall syntax for DSL marker annotation
DSL marker annotations must be annotated with@DslMarkerand declared as annotation classes.Step 2: Identify correct syntax
@DslMarker annotation class MyDslMarker correctly declares an annotation class with@DslMarkerannotation:@DslMarker annotation class MyDslMarker.Final Answer:
@DslMarker annotation class MyDslMarker -> Option DQuick Check:
DSL marker annotation syntax = A [OK]
- Defining DSL marker as a function instead of annotation class
- Missing the @DslMarker annotation on the annotation class
- Using incorrect placement of @DslMarker keyword
@DslMarker
annotation class FirstDsl
@DslMarker
annotation class SecondDsl
@FirstDsl
class BuilderA {
fun a() = println("A")
fun buildB(block: BuilderB.() -> Unit) {
BuilderB().apply(block)
}
}
@SecondDsl
class BuilderB {
fun b() = println("B")
fun buildA(block: BuilderA.() -> Unit) {
BuilderA().apply(block)
}
}
fun main() {
BuilderA().apply {
a()
buildB {
b()
// Which call is allowed here?
// a() or b() or both?
}
}
}
Which function call is allowed inside the buildB block?Solution
Step 1: Understand effect of multiple @DslMarker annotations
Each DSL marker annotation restricts visibility of outer scope functions from classes annotated with different DSL markers.Step 2: Analyze nested block visibility
InsidebuildBblock (BuilderB scope), onlyb()is visible. Thea()function from BuilderA is hidden due to different DSL markers.Final Answer:
Onlyb()is allowed;a()is not visible due to DSL marker scope control. -> Option CQuick Check:
Different DSL markers hide outer scope functions = C [OK]
- Assuming all functions from outer scopes are visible
- Ignoring effect of multiple DSL markers
- Confusing which DSL marker applies where
@DslMarker
annotation class MyDsl
@MyDsl
class Outer {
fun outerFun() = println("Outer")
fun inner(block: Inner.() -> Unit) {
Inner().apply(block)
}
}
@MyDsl
class Inner {
fun innerFun() = println("Inner")
}
fun main() {
Outer().apply {
outerFun()
inner {
outerFun() // Error here
innerFun()
}
}
}Solution
Step 1: Identify cause of error
Inside the inner block,outerFun()is not directly visible because@MyDslrestricts access to outer scope functions.Step 2: Fix by explicit qualification
Use explicit labelthis@Outer.outerFun()to call the outer function inside the inner block, bypassing DSL marker restriction.Final Answer:
Error: Cannot call outerFun() inside inner block due to DSL marker scope control; fix by qualifying with this@Outer.outerFun(). -> Option BQuick Check:
Use explicit this@Outer to access outer functions [OK]
- Removing @DslMarker annotation instead of qualifying call
- Ignoring the need for explicit receiver labels
- Assuming innerFun() visibility causes error
HtmlBuilder and BodyBuilder, both annotated with the same @DslMarker annotation. How does using @DslMarker help prevent mistakes when writing nested DSL code like html { body { div { ... } } }?Solution
Step 1: Understand nested DSL structure
In nested DSLs like HTML, inner blocks (BodyBuilder) are inside outer blocks (HtmlBuilder).Step 2: Role of @DslMarker in nested builders
@DslMarker annotation restricts visibility of outer builder functions inside inner builder blocks, preventing accidental calls to outer scope functions without explicit qualification.Final Answer:
It prevents calling functions from HtmlBuilder inside BodyBuilder blocks unless explicitly qualified, avoiding accidental outer scope calls. -> Option AQuick Check:
@DslMarker hides outer builder functions inside nested blocks [OK]
- Thinking @DslMarker generates code automatically
- Assuming all functions remain visible inside nested blocks
- Believing @DslMarker disables nesting
Ready to prove it?
Pass the test and this pattern moves unknown
