Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

kotlin inner DSL that use infix functions to add more clarity

Tags:

kotlin

kotlin 1.2.71

I have the following DSL I am writing and want to get it as close to this sample as possible:

android {
      compileSdkVersion 26
      buildToolsVersion "28.0.3"
}

I am doing this using infix functions and lambda and receivers. Inside my DSL I have to use the it keyword otherwise I can't get the infix function to work. Just wondering, if there was a way to do this?

Another question: In my infix function do I need to use it as an extension function and prefix it with the keyboard Android i.e. infix fun Android.buildToolsVersion(...)

fun main(args: Array<String>) {

    val androidConfig = android {
        it buildToolsVersion  "28.0.3"
        it compileSdkVersion 26
    }

    println(androidConfig.toConsolePrint)
}

private fun android(block: Android.(Android) -> Unit): Android {
    val android = Android()

    android.block(android)

    return android
}

class Android(var compileSdkVersion: Int = 0,
              var buildToolsVersion: String = "") {

    infix fun Android.buildToolsVersion(buildToolsVersion: String) {
        this.buildToolsVersion = buildToolsVersion
    }

    infix fun Android.compileSdkVersion(sdkVersion: Int) {
        compileSdkVersion = sdkVersion
    }
}

private val Android.toConsolePrint: String
    get() {
        return "compileSDK: $compileSdkVersion build tools: $buildToolsVersion"
    }
like image 201
ant2009 Avatar asked Oct 24 '18 07:10

ant2009


1 Answers

To clarify: you do not need the it for the infix to work. You could also just have written this instead, e.g. this compileSdkVersion 26.

Note also that it rather suffices to just have the following in place:

fun android(block: Android.() -> Unit) = Android().apply(block)
class Android(var compileSdkVersion: Int = 0,
              var buildToolsVersion: String = "")

The usage then only differs by a = to what you have shown:

val androidConfig = android {
  buildToolsVersion = "28.0.3"
  compileSdkVersion = 26
}

But the benefit is way bigger: much less code to maintain ;-)

Regarding the other point you mentioned, that you need to supply extension functions. You didn't need to. It suffices to just place the infix fun Android.buildToolsVersion inside of the class Android without the Android. and it would have worked, e.g.:

class Android(var compileSdkVersion: Int = 0,
              var buildToolsVersion: String = "") {
  infix fun buildToolsVersion(buildToolsVersion: String) {
    this.buildToolsVersion = buildToolsVersion
  }

Now regarding consolePrint. That looks also rather complicated to me. What about the following instead inside the class Android?

fun toConsolePrint() = "compileSDK: $compileSdkVersion build tools: $buildToolsVersion"

That's what functions are actually used for... of course: if you do not like the brackets, you can still use your approach with the get(), but it sounds like a function, it acts like a function, so it probably also is a function ;-)

The complete sample with only difference being added () and =:

fun main() {
  val androidConfig = android {
    buildToolsVersion = "28.0.3"
    compileSdkVersion = 26
  }
  println(androidConfig.toConsolePrint())
}

fun android(block: Android.() -> Unit) = Android().apply(block)
class Android(var compileSdkVersion: Int = 0,
              var buildToolsVersion: String = "") {
  fun toConsolePrint() = "compileSDK: $compileSdkVersion build tools: $buildToolsVersion"
}
like image 89
Roland Avatar answered Oct 27 '22 19:10

Roland