Type-Safe Builders

    Type-safe builders allow creating Kotlin-based domain-specific languages (DSLs) suitable for building complex hierarchical data structures in a semi-declarative way. Some of the example use cases for the builders are:

    • Generating markup with Kotlin code, such as HTML or XML;
    • Programmatically laying out UI components:
    • Configuring routes for a web server: Ktor.

    Consider the following code:

    This is completely legitimate Kotlin code. You can play with this code online (modify it and run in the browser) .

    Let’s walk through the mechanisms of implementing type-safe builders in Kotlin. First of all, we need to define the model we want to build, in this case we need to model HTML tags. It is easily done with a bunch of classes. For example, HTML is a class that describes the <html> tag, i.e. it defines children like <head> and <body>. (See its declaration below.)

    Now, let’s recall why we can say something like this in the code:

    1. html {
    2. // ...
    3. }

    html is actually a function call that takes a as an argument. This function is defined as follows:

    1. fun html(init: HTML.() -> Unit): HTML {
    2. val html = HTML()
    3. html.init()
    4. return html
    5. }

    This function takes one parameter named init, which is itself a function. The type of the function is HTML.() -> Unit, which is a function type with receiver. This means that we need to pass an instance of type HTML (a receiver) to the function, and we can call members of that instance inside the function. The receiver can be accessed through the this keyword:

    1. html {
    2. this.head { ... }
    3. this.body { ... }
    4. }

    (head and body are member functions of HTML.)

    Now, this can be omitted, as usual, and we get something that looks very much like a builder already:

    1. html {
    2. head { ... }
    3. body { ... }
    4. }

    The head and body functions in the HTML class are defined similarly to html. The only difference is that they add the built instances to the children collection of the enclosing HTML instance:

    Actually these two functions do just the same thing, so we can have a generic version, initTag:

    1. protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
    2. tag.init()
    3. children.add(tag)
    4. return tag
    5. }

    So, now our functions are very simple:

    1. fun head(init: Head.() -> Unit) = initTag(Head(), init)
    2. fun body(init: Body.() -> Unit) = initTag(Body(), init)

    And we can use them to build <head> and <body> tags.

    One other thing to be discussed here is how we add text to tag bodies. In the example above we say something like:

    1. html {
    2. head {
    3. title {+"XML encoding with Kotlin"}
    4. }
    5. // ...
    6. }

    So basically, we just put a string inside a tag body, but there is this little + in front of it, so it is a function call that invokes a prefix unaryPlus() operation. That operation is actually defined by an extension function unaryPlus() that is a member of the TagWithText abstract class (a parent of Title):

    1. operator fun String.unaryPlus() {
    2. children.add(TextElement(this))

    So, what the prefix + does here is wrapping a string into an instance of TextElement and adding it to the children collection, so that it becomes a proper part of the tag tree.

    All this is defined in a package com.example.html that is imported at the top of the builder example above. In the last section you can read through the full definition of this package.

    When using DSLs, one might have come across the problem that too many functions can be called in the context. We can call methods of every available implicit receiver inside a lambda and therefore get an inconsistent result, like the tag head inside another head:

    To address this problem, in Kotlin 1.1 a special mechanism to control receiver scope was introduced.

    To make the compiler start controlling scopes we only have to annotate the types of all receivers used in the DSL with the same marker annotation. For instance, for HTML Builders we declare an annotation @HTMLTagMarker:

    1. @DslMarker
    2. annotation class HtmlTagMarker

    An annotation class is called a DSL marker if it is annotated with the @DslMarker annotation.

    In our DSL all the tag classes extend the same superclass Tag. It’s enough to annotate only the superclass with @HtmlTagMarker and after that the Kotlin compiler will treat all the inherited classes as annotated:

    1. @HtmlTagMarker
    2. abstract class Tag(val name: String) { ... }

    We don’t have to annotate the HTML or Head classes with @HtmlTagMarker because their superclass is already annotated:

    1. class HTML() : Tag("html") { ... }
    2. class Head() : Tag("head") { ... }

    After we’ve added this annotation, the Kotlin compiler knows which implicit receivers are part of the same DSL and allows to call members of the nearest receivers only:

    1. html {
    2. head {
    3. }
    4. // ...
    5. }

    Note that it’s still possible to call the members of the outer receiver, but to do that you have to specify this receiver explicitly:

    This is how the package com.example.html is defined (only the elements used in the example above). It builds an HTML tree. It makes heavy use of extension functions and .

    Note that the @DslMarker annotation is available only since Kotlin 1.1.

    1. package com.example.html
    2. interface Element {
    3. fun render(builder: StringBuilder, indent: String)
    4. }
    5. class TextElement(val text: String) : Element {
    6. override fun render(builder: StringBuilder, indent: String) {
    7. builder.append("$indent$text\n")
    8. }
    9. }
    10. @DslMarker
    11. annotation class HtmlTagMarker
    12. @HtmlTagMarker
    13. abstract class Tag(val name: String) : Element {
    14. val children = arrayListOf<Element>()
    15. val attributes = hashMapOf<String, String>()
    16. protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
    17. tag.init()
    18. children.add(tag)
    19. return tag
    20. }
    21. override fun render(builder: StringBuilder, indent: String) {
    22. builder.append("$indent<$name${renderAttributes()}>\n")
    23. for (c in children) {
    24. c.render(builder, indent + " ")
    25. }
    26. }
    27. private fun renderAttributes(): String {
    28. val builder = StringBuilder()
    29. for ((attr, value) in attributes) {
    30. builder.append(" $attr=\"$value\"")
    31. }
    32. return builder.toString()
    33. }
    34. override fun toString(): String {
    35. val builder = StringBuilder()
    36. render(builder, "")
    37. }
    38. }
    39. abstract class TagWithText(name: String) : Tag(name) {
    40. operator fun String.unaryPlus() {
    41. children.add(TextElement(this))
    42. }
    43. }
    44. class HTML : TagWithText("html") {
    45. fun head(init: Head.() -> Unit) = initTag(Head(), init)
    46. fun body(init: Body.() -> Unit) = initTag(Body(), init)
    47. }
    48. class Head : TagWithText("head") {
    49. fun title(init: Title.() -> Unit) = initTag(Title(), init)
    50. }
    51. class Title : TagWithText("title")
    52. abstract class BodyTag(name: String) : TagWithText(name) {
    53. fun b(init: B.() -> Unit) = initTag(B(), init)
    54. fun p(init: P.() -> Unit) = initTag(P(), init)
    55. fun h1(init: H1.() -> Unit) = initTag(H1(), init)
    56. fun a(href: String, init: A.() -> Unit) {
    57. val a = initTag(A(), init)
    58. a.href = href
    59. }
    60. }
    61. class Body : BodyTag("body")
    62. class B : BodyTag("b")
    63. class P : BodyTag("p")
    64. class H1 : BodyTag("h1")
    65. class A : BodyTag("a") {
    66. var href: String
    67. get() = attributes["href"]!!
    68. set(value) {
    69. attributes["href"] = value
    70. }
    71. }
    72. fun html(init: HTML.() -> Unit): HTML {
    73. val html = HTML()
    74. html.init()
    75. return html