The concept of builders is rather popular in the Groovy community. Builders allow for defining data in a semi-declarative way. Builders are good for generating XML, laying out UI components, describing 3D scenes and more...
For many use cases, Kotlin allows to type-check builders, which makes them even more attractive than the dynamically-typed implementation made in Groovy itself.
For the rest of the cases, Kotlin supports [dynamic] builders.
A type-safe builder example
Consider the following code that is taken from here and slightly adapted:
fun result(args : Array<String>) = } // an element with attributes and text content // mixed content } // content generated by for (arg in args) } } }This is a completely legitimate Kotlin code. Click on names to navigate to definitions of function used in this example (they appear below in this page).
How it works
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:
html { // ... }This is actually a function call that takes a function literal as an argument (see this page for details). Actually, this function is defined as follows:
fun html(init : HTML.() -> Unit) : HTML { val html = HTML() html.init() return html }This function takes one parameter named init, which is itself a function. Actually, it is an extension function that has a receiver of type HTML (and returns nothing interesting, i.e. Unit). So, when we pass a function literal to as an argument to html, it is typed as an extension function literal, and there's this reference available:
html { this.head { /* ... */ } this.body { /* ... */ } }(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:
html { head { /* ... */ } body { /* ... */ } }So, what does this call do? Let's look at the body of html function as defined above. It creates a new instance of HTML, then it initializes it by calling the function that is passed as an argument (in our example this boils down to calling body on the HTML instance), and then it returns this instance. This is exactly what a builder should do.
The head and body functions in the HTML class is defined similarly to html. The only difference is that they add the built instanced 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:
protected fun initTag<T : Element>(init : T.() -> Unit) : T where class object T : Factory<T> { val tag = T.create() tag.init() return tag }This function uses class objects to instantiate classes. It depends on the Factory class defined as follows:
abstract class Factory<T> { fun create() : T }Now, the classes Head and Body declare class objects that extend Factory, for example:
class Head() : TagWithText("head") { class object : Factory<Head> { override fun create() = Head() } // ... }So, now our functions are very simple:
fun head(init : Head.() -> Unit) = initTag(init) fun body(init : Body.() -> Unit) = initTag(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
So basically, we just put a string inside a tag body, but there is this little "+" in front of it, do it is a function call that invokes a prefix "plus" operation. That operation is actually defined by an extension function plus that is a member of the TagWithText abstract class (a parent of Title):
So, what the prefix "+" does here is it wraps a string into an instance of TextElement and adds it to the children collection, so that it becomes a proper part of the tag tree.
All this is defined in a namespace html that is imported at the top of the builder example above. In the next section you can read through the full definition of this namespace.
Full definition of the html namespace
This is how the namespace html is defined (only the elements used in the example above). It builds an HTML tree. It makes heavy use of Extension functions and Extension function literals.
fun create() : T } protected fun initTag<T : Element>(init : T.() -> Unit) : T where class object T : Factory<T> { val tag = T.create() tag.init() return tag } } children.add(TextElement(this)) } } class object : Factory<HTML> { override fun create() = HTML() } } class object : Factory<Head> { override fun create() = Head() } } } class object : Factory<Body> { override fun create() = Body() } val a = initTag(init) a.href = href } } class B() : BodyTag("b") class P() : BodyTag("p") class H1() : BodyTag("h1") class A() : BodyTag("a") { var href : String } val html = HTML() html.init() return html } }Appendix. Making Java classes nicer
In the code above there's something that looks very nice:
class A() : BodyTag("a") { var href : String }We access the attributes map as if it were an "associative array": just with the [] operation. By convention this compiles to a call to get(K) or set(K, V), all right. But we said that attributes was a Java Map, i.e. it does NOT have a set(K, V). This problem is easily fixable in Kotlin:
fun <K, V> Map<K, V>.set(key : K, value : V) = this.put(key, value)So, we simply define an extension function set(K, V) that delegates to vanilla put and make a Kotlin operator available for a Java class.
What's next
Examples
Functions