DEV Community

Ahmed Rizwan
Ahmed Rizwan

Posted on

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
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 !
Enter fullscreen mode Exit fullscreen mode

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) } 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Combining These!

Captain Planet

Let’s now create a DSL of our own. Just to demonstrate:

val myProfile = "Ahmed Rizwan" profile {
    age = 90
    phone = "123 456 789"      
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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.

Happy coding!

Top comments (0)