Using Kotlin for Data Builders

In Kotlin, functions whose last argument is a lambda, i call them end-lambda’s, make natural expressions of tree-shaped data builders.

Say you’re building Performances, where a Performance includes (possibly) the list of songs being performed. making shit up here, it might look like:

performance { 
song { 
}
song { 
}
}

First thing you notice: this thing is building some tree-shaped data, and it’s tree-shaped. None of this "song1= … song2= … performance = Performance(song1, song2) shit.

If you’re new to kotlin and lambdas, you may not even know what "performance" is up there. what it is something like:

performance( addDetails: PerformanceBuilder.() ->Unit = {})

Bizarre, yeah? That says: performance is a method that takes a lambda, and the lambda has to be a member or extension method of a PerformanceBuilder. The last little "= {}" clause says "the lambda is optional, if they don’t wanna give details, just keep on going".

So what’s "song" then? Well. song is in fact a member function of that PerformanceBuilder gal. The performance starts, when we hit the first curly, we’re now in a PerformanceBuilder, and we can freely call methods or assign fields in that PerformanceBuilder.
When we hit the closing curly for performance, we’ll have it finally call that PerformanceBuilder’s "build" function. it’ll return a brand new shiny performance with whatever details we’ve added.

So the full definition of performance is:

performance( addDetails: PerformanceBuilder.() ->Unit = {}):Performance {
val builder = PerformanceBuilder()
builder.addDetails()
return builder.build()
}

Making builders this way can be as simple or complex as we want. We can make PerformanceBuilder start with default values for its fields, and let the addDetails change them. We can repeatedly nest this crap, to build n-deep trees.

We can also assign artists to a performance:

performance { 
song {
}
artist {
}
}

We don’t have to have these builder methods only take a lambda.

performance(LIVE, VIDEO, FLOOR) { … } might mark this performance with all three tags, like Black Dub’s live on the floor video of "I Believe In You".

If you’re clever, you can make the builders share data as they work down the tree. If we had album-performer-performance-song tree, we could make the performer apply to all of the performances that follow it, until we change it, for instance.

album("Houses of the Holy") {
performer { ... led zeppelin }
performance { }
performance { }
performance { }
|

When we go to an anthology album, it could be

album("some anthology") {
performer { }
performance { }
performance { }
performer { }
performance { }
}

U can arrange for it to be sequenced like this, or u could arrange for it to be nested, where you first have to supply a performer, then a bunch of performances "inside" it. Either way works, it’s up to the DSL-maker’s understanding of the domain.

All of this derives from just three features of kotlin: end-lambdas, default argument values, and easy description of member-lambdas.

I didn’t of course invent this. I liberated the technique from Edvin Syse’s excellent tornadofx, which is basically an excellent kotlin-style way to do javafx apps. I got the juice to learn how to do it cuz I have hundreds of tests in the dayjob that set up 6-deep trees.

An area we haven’t yet broached: it’s often the case underlying elements of such a dataset are meant — for your app — to be in some set of upstream sources. That is, performances come from the performance server, and they include song id’s, but songs from from the song server.

And that’s where the builders shine: somewhere in your app there is a place where all calls to the performance server cache their results. Ditto the songs. The builder classes can do things like "hook it all up". That is, when the closing curly is hit, they take what they got and stuff it in the upstream cache, as if the external service had already sent it.

Sometimes little kids say to one another, "play like…". Play like I’m a doctor and you’re a patient. Play like I’m the grocer and you’re a customer who wants beans.

This feature of using builders is a form of "play like". Play like the performance upstream knows this, and the song upstream that.

There is great power here. You don’t have to tell the builder the id’s. It knows it needs them and how to put them in the places they line up.

Leave a Reply