Is it possible to easily build DSL in Kotlin? - kotlin

TypeScript allows very nice and clean and 100% type-safe way to build data-like DSLs. I wonder if it's possible in Kotlin?
For example, in TypeScript code below (playground) we defining columns for data table. It checks that values are correct (string enums), checks all the optional / required fields, has autocomplete etc. And it just works out of the box, all you need to do is define types.
Is it possible to use something like that in Kotlin? It's possible to use Java Builder-pattern, but it's not ideal, and it requires to write lots of code for builder-methods. Also, Kotlin doesn't have a way to use "number" enum, it would be Type.number, doesn't look nice. Or maybe I'm missing something and there's a way to build nice and clean DSL in Kotlin without too much boilerplate code?
// Defining DSL ---------------------------------------------
type Type = "string" | "number" | "boolean" | "unknown"
interface StringFormatOptions {
type: "string"
}
interface LineFormatOptions {
type: "line"
ticks?: number[]
}
interface Column {
type: Type
format?: StringFormatOptions | LineFormatOptions
}
// Using DSL ------------------------------------------------
const columns: Column[] = [
{
type: "number",
format: { type: "line", ticks: [1000] }
},
{
type: "string"
}
]

Yes, you can create type-safe DSLs in Kotlin. It may be tricky to understand at first, but it really become very easy when you get used to it.
It works by creating functions that receive lambdas which have a specific receiver type... Well... let's try again. Assuming you are the user of already existing DSL, this is what happens:
There is a function that requires providing a lambda to it.
You provide a lambda.
The function provides a this parameter of a specific type to your lambda.
You can use properties/functions of provided this object in the lambda, effectively making possible to go deeper into DSL chain.
Let's see this example:
fun copy(init: CopyBuilder.() -> Unit) { TODO() }
interface CopyBuilder {
var from: String
var to: String
fun options(init: CopyOptionsBuilder.() -> Unit) { TODO() }
}
interface CopyOptionsBuilder {
var copyAttributes: Boolean
var followSymlinks: Boolean
}
We have a copy() function which receives a lambda. Provided lambda will have access to CopyBuilder object as this, so it will have access to e.g. from and to properties. By calling options() from the lambda we move deeper and now we have access to CopyOptionsBuilder object.
copy() is responsible for providing a proper implementation of CopyBuilder object to your lambda. Similarly, implementation of options() need to provide a proper implementation of CopyOptionsBuilder. This was omitted from the example above.
Then it can be used as:
copy {
from = "source"
to = "destination"
options {
copyAttributes = true
followSymlinks = false
}
}
If you use Gradle with Kotlin DSL then build.gradle.kts file is actually a regular Kotlin file. It just starts with some variables provided to you. Another good example of DSL in Kotlin is kotlinx.html library. It generates HTML code with syntax like this:
html {
body {
div {
a("https://kotlinlang.org") {
target = ATarget.blank
+"Main site"
}
}
}
}
You can read more about this here: https://kotlinlang.org/docs/type-safe-builders.html

Related

Javascript structure representation using Kotlin JS

my question is about creating javascript structure within KotlinJS and use them calling external modules.
Let's say we have the following js code and we want to translate it into KotlinJS.
const config = {
defs : "something",
resolvers : {
Query: {
books: () => []
}}
};
myFunction(config) // This can be any kind of external js function that accepts the above structure
How do we represent that config structure above using Kotlin JS? Is there an easy way to handle structures/json Kotlin side? Can we declare in some way that structure as dynamic?
Using Kotlin objects doesn't help.
As of now you gotta introduce interface and it's implementation, so it would be something like this:
external interface ConfigInterface {
var defs: String,
var resolvers: QueryHolder
}
external interface QueryHolder {
var Query: BookProcessor
}
external interface BookProcessor {
var books: () -> Array<Any>
}
For more complicated structures it can easily become a challenge.
Here's what can be done to automate such translations.
You can:
generate typescript declaration for this code with typescript compiler (using tsc -d)
generate kotlin declaration with dukat.
Dukat is a tool from Kotlin/JS team created specifically for this, there's ongoing battle for improving the quality of this tool. Here is what would be generated in your particular case:
external interface `T$0` {
var books: () -> Array<Any>
}
external interface `T$1` {
var Query: `T$0`
}
external object config {
var defs: String
var resolvers: `T$1`
}
Which is far from optimal - for instance the name of generated entities is something we didn't wanted to encourage people to reuse but the more it goes, the more it looks like a mistake (which we will fix this way or another).

How does Kotlin recognize a lambda receiver in a "use" function

When I look at sample code for the "use" function in Kotlin, I usually see something like this:
private fun readFirstLine(): String {
BufferedReader(FileReader("test.file")).use { return it.readLine() }
}
However, in the following example, I don't understand where "input" comes from, since input -> appears to be a lambda. From my understanding, everything inside of use { } must be an expression:
val streamIn = resources.openRawResource(rawResId)
val streamOut = FileOutputStream(destFilename)
streamIn.use { input ->
streamOut.use { output ->
input.copyTo(output)
}
}
"input" clearly refers to the same object that "streamIn" refers to, but I don't understand how Kotlin knows that.
everything inside of use { } must be an expression
If you looked at the signature, you'll see that use takes a (T) -> R function, so really, any function/lambda that accepts the closable thing as a parameter can be passed to it.
With that misconception cleared up, let's see what the code in question is doing.
streamIn.use { input ->
streamOut.use { output ->
input.copyTo(output)
}
}
First we see streamIn.use {, which means we are going to do something with streamIn and then close it. And from now on streamIn will be called input. Then there is streamOut.use {, which indicates that we are also going to use streamOut to do stuff, and then close it, and we are going to call it output from now on.
I don't understand where "input" comes from
It's basically giving another name to the it as in your first code snippet. Since we have nested lambdas here, we can't use it to refer to the parameters of both lambdas.
"input" clearly refers to the same object that "streamIn" refers to, but I don't understand how Kotlin knows that.
This is because in the implementation of use, there's probably a line like this:
return block(this)
block is the lambda parameter you pass to use, and this is the object on which use is called. Since input is the parameter of the lambda, it refers to this.
Now we have declared that we are going to use two resources, what are going to do with them? input.copyTo(output)! Whatever copyTo returns is going to be returned by streamOut.use, which in turn is going to be returned by streamIn.use. streamOut and streamIn will also be closed one after another.
So overall what have we done? We have basically used 2 resources at the same time and closed them afterwards. This is how you'd compose use to use multiple resources at the same time.
in the lambda, you can define a name for your object so in the following code the input is equivalent to it which is streamIn and output is equivalent to streamOut:
streamIn.use { input ->
streamOut.use { output ->
input.copyTo(output)
}
}
The reason that they define input and output is you cannot use it when you use a lambda block inside another lambda block.
use is an extension function which takes whatever calls it as a parameter.
Assume this example:
file.bufferedReader().use{
println(it.readText()) // it is actually that object that calls `use`
}
According to the API docs of Kotlin, this is the schema of use:
inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R
The bufferedReader in my example is a closable class.
When you write somethingClosable.use { }, you are in fact passing a lambda function to it, like:
fun <T, R> function(t: T): R {
// use T and return an R
}
somethingClosable.use(function)
And inside use your function will be called.
More info on extension functions in Kotlin.

Can you concatenate statements at run-time in Kotlin?

I am trying to interface with TeamCity using Kotlin-DSL
In this section of the TC Kotlin guide there is a rather odd looking part where it seems like it causes statements to become concatenated on the fly.
It first defines these:
val linux = Requirements() {
contains("os.name", "linux")
}
val oracle = Requirements() {
equals("db.name", "oracle")
}
val java6 = Requirements() {
contains("env.JAVA_HOME", "1.6")
}
Then does this with those definitions:
buildType {
...
requirements(linux + oracle + java6)
...
}
I know that the above section of code is equivalent to
buildType {
...
requirements {
contains("os.name", "linux")
equals("db.name", "oracle")
contains("env.JAVA_HOME", "1.6")
}
...
}
So I suppose what my question boils down to is what is the return type of the 'Requirements' function that can just be concatenated together? My guess is it is some sort of statement/ function wrapper and Kotlin lets you concatenate these as you go, and the function signature looks like this:
fun Requirements(init: (a: String, b: String) -> UnknownTypeA) : UnknownTypeB
EDIT:
For anyone who is confused when reading this in the future, the calls to Requirements are actually an object initialisation via the Requirements constructor. I do inevitably feel embarrassed for not picking up on this (The casing of the name should have been hint enough!) but I'm making this edit to make it clear to people that it is not a function. Thank you to Hotkey for pointing that out.
First, note that Requirements accepts a function into its constructor. Without knowing what is the type of that function, let's assume it's Context.() -> Unit (a function with receiver of Context, accepting no arguments and returning Unit).
Now, we can naturally overload the plus operator for the Requirements type, so that it returns another Requirements instance that has a function that applies both functions from the operands.
You could do that in your own code in the following way:
class Requirements(val check: Context.() -> Unit)
operator fun Requirements.plus(other: Requirements) =
Requirements { check(); other.check() }

Kotlin idiom for working with non-null object and non-blank String representation

I have a nullable property (a Java object) that knows how to convert itself to a String, and if this representation is not empty, I would like to do something with it. In Java this looks like:
MyObject obj = ...
if (obj != null) {
String representation = obj.toString();
if (!StringUtils.isBlank(representation)) {
doSomethingWith(representation);
}
}
I'm trying to find the most idiomatic way of converting this to Kotlin, and I have:
with(obj?.toString()) {
if (!isNullOrBlank()) {
doSomethingWith(representation)
}
}
But it still feels like too much work for such a simple operation. I have this feeling that combining let, when, and with I can slim this down to something a bit shorter.
The steps are:
If the object (A) is not null
If the String representation (B) of object (A) is not blank
Do something with (B)
I tried:
when(where?.toString()) {
isNullOrBlank() -> builder.append(this)
}
But (1) it fails with:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: #InlineOnly public inline fun
CharSequence?.isNullOrBlank(): Boolean defined in kotlin.text #InlineOnly public inline fun CharSequence?.isNullOrBlank(): Boolean defined in
kotlin.text
And even if it got past that, (2) it would want the exhaustive else, which I don't really care to include.
What's the "Kotlin way" here?
You can use the (since Kotlin 1.1) built-in stdlib takeIf() or takeUnless extensions, either works:
obj?.toString().takeUnless { it.isNullOrBlank() }?.let { doSomethingWith(it) }
// or
obj?.toString()?.takeIf { it.isNotBlank() }?.let { doSomethingWith(it) }
// or use a function reference
obj?.toString().takeUnless { it.isNullOrBlank() }?.let(::doSomethingWith)
For executing the action doSomethingWith() on the final value, you can use apply() to work within the context of the current object and the return is the same object, or let() to change the result of the expression, or run() to work within the context of the current object and also change the result of the expression, or also() to execute code while returning the original object.
You can also create your own extension function if you want the naming to be more meaningful, for example nullIfBlank() might be a good name:
obj?.toString().nullIfBlank()?.also { doSomethingWith(it) }
Which is defined as an extension to a nullable String:
fun String?.nullIfBlank(): String? = if (isNullOrBlank()) null else this
If we add one more extension:
fun <R> String.whenNotNullOrBlank(block: (String)->R): R? = this.nullIfBlank()?.let(block)
This allows the code to be simplified to:
obj?.toString()?.whenNotNullOrBlank { doSomethingWith(it) }
// or with a function reference
obj?.toString()?.whenNotNullOrBlank(::doSomethingWith)
You can always write extensions like this to improve readability of your code.
Note: Sometimes I used the ?. null safe accessor and other times not. This is because the predicat/lambdas of some of the functions work with nullable values, and others do not. You can design these either way you want. It's up to you!
For more information on this topic, see: Idiomatic way to deal with nullables

Kotlin type safe builder DSLs, safety for the outermost function

I'm going to use the official example from the documentation that implements a DSL for some HTML creation.
Since Kotlin 1.1, the #DslMarker annotation allows us to restrict the scope of the functions in our classes, like the example does with the #HtmlTagMarker annotation. This gives us an error when trying to write incorrectly structured code like this:
html {
body {
body { // this in an error, as it's a function call on the outside Html element
}
}
}
However, this doesn't prevent nesting the outermost function, which is the entry point to the DSL. For example, with the example as it is now, this can be written down without problems:
html {
html {
}
}
Is there any way to make a DSL safer in this regard?
Probably this can somehow be done in a more elegant way, but I can suggest using the #Deprecated annotation with DeprecationLevel.ERROR on a function with a matching signature defined for the receiver type, for example:
#Deprecated("Cannot be used in a html block.", level = DeprecationLevel.ERROR)
fun HtmlReceiver.html(action: HtmlReceiver.() -> Unit): Nothing = error("...")
Or this can be a member function. By the way, the IDE completion behaves a bit differently based on whether it is an extension or a member.
This will make the calls like the inner one invalid:
html {
html { // Error: Cannot be used in a html block.
}
}
(demo of this code)
The top-level function can still be called inside a DSL block by its FQN e.g. com.example.html { }, so this trick only protects the users from calling the top level function by mistake.