Quick Recipe for creating DSLs in Kotlin
A pretty cool feature in Kotlin is the ability to construct custom DSLs. And you need just four things in order to write them.
- Infix Notations
- Extension Methods
- Lambdas
- Lambda with Receiver
Let’s go through these one by one.
Infix Notation
Remove brackets & dots!
class Car {
fun drive(miles: Int) {
...
}
}
val car = Car()
car.drive(10) // normal stuff
Just add infix notation before method declaration for it to work
class Car {
infix fun drive(miles: Int) {
...
}
}
val car = Car()
car drive 10 // no brackets! no dots!
Extension Methods
Add new functions to any class without having to inherit!
val name = "Ahmed Riz"
name.shout() // won't compile as shout() is not part of String class
In order to make it a part of String class (without inheriting), create shout method prefixed with “String.”
fun String.shout() {
println("\$this !") // this refers to the value of string itself
}
val name = "Ahmed Riz"
name.shout() // works! and prints out: Ahmed Riz !
Extension methods are project-scoped — you can access them anywhere inside the project.
Lambdas
Pass anonymous function literals (to higher order functions)!
// a higher order function
fun process(value: Int, operation: (Int) -> Unit) {
operation(value)
}
// when calling, we can pass in a lambda expression
process(10, { value -> println(value) })
// and because it's the last param, we can extract it out
// just to clean things up even further
process(10) { value -> println(value) }
Lambda with Receiver
Pass anonymous function literals (to higher order functions), but with a receiver type!
// slight change to the previous higher order function
// now instead of operation: (Int) -> Unit
// we'll do operation: Int.() -> Unit
// This makes Int the receiver type
fun process(value: Int, operation: Int.() -> Unit) {
value.operation()
}
// usage now becomes
process(10) { println(this) } // prints out: 10
Combining These!
Let’s now create a DSL of our own. Just to demonstrate
val myProfile = "Ahmed Rizwan" profile {
age = 90
phone = "123 456 789"
}
Code in order to make it a valid DSL is as simple as this
class Profile(
val name: String,
var age: Int? = null,
var phone: String? = null
)
infix fun String.profile(create: Profile.() -> Unit): Profile {
val profile = Profile(this)
profile.create()
return profile
}
And that’s it!
Although the DSL above might not be a very useful one in real world — but hopefully it gives you an idea of how we can utilize these different Kotlin features to create some custom DSLs of our own.