Kotlin DSL support is awesome!
One of my favorites features of Kotlin, is the powerful DSL(ish) support the language provides.
You can write very expressive functions that look almost like a DSL, all because in Kotlin if the last argument of a function is a lambda you can move it outside the parentheses into curly brackets {}
This allows you to write code that almost look like a markdown representation such as the HTML
builder that ships with the language:
html {
head {
title {+"XML encoding with Kotlin"}
}
body { }
}
This code looks like a markdown, but it's a static compiled piece of code. Isn't that nice?
One of my favorites usages of DSLs in kotlin is a project that wraps kubernetes-client (which uses a lot of builders) into nice friendly DSL. Check it out: k8s-kotlin-dsl
How I got addicted to it
After a while I started to using DSLs more and more. If you use co-routines you probably saw this DSL-esque function:
fun testAsyncFunction() = runBlocking {
//Some async code goes here
}
Another one that I enjoy quite a lot is to replace all those: System.currentTimeInMillis()
we all have on our code:
val runningTime = measureTimeInMillis {
//invoke function here
}
As you can see, using DSLs can make your code easier to read, one can easily infer what is happening at measureTimeInMillis
or in runBlocking
So because of that I started creating a few of my own.
Being a nice API consumer
Another area where Kotlin shines is concurrency with co-routines (as well as parallelism with async).
In one of my pet projects I crawl an external public API that has a rate-limit policy. No limits, as long as you space your calls every 2 seconds.
Since I create a gazillion co-routines (actually more like 3k) and I want them to run in parallel not writing a delay(2000)
on each function. I decided to use an implementation of a TokenBucket - Wikipedia : Github repo.
So I wanted all my functions that were running inside an async
block to be limited by a given tokenbucket. So the idea was that any function should look like this:
fun request() : ApiResponse<T> = limited {
}
This would allow anyone reading this code to see that this call, is limited by a certain factor.
The limited function is very simple:
suspend fun <T> limited(bucket: TokenBucket = DEFAULT_LIMITER, block: suspend () -> T) : T {
bucket.consume()
return block()
}
The block parameter is the function you are wrapping between limited { <block> }
, which returns whatever the type your original function returns. We call this higher order functions.
Because all the code is supposed to run on a suspended state, both the function and the block are marked as suspend
.
Now I can run this code inside a loop like this:
val pages = getNumberOfPages()
for(p in 0..pages){
GlobalScope.launch {
val response request(p)
//process response
}
//return immediately to caller
And I'm sure that all thousands of jobs in flight will be limited by only 1 call every 2 seconds.
Final thoughts
It's becoming pretty easy to understand why kotlin is such a beloved language. Small features like this make your code so much more readable. It's pretty neat to be able to express your code in a clear way.
Happy coding
Top comments (0)