Gradle - (3) Kotlin DSL

疑惑

第一眼看到Gradle 的 Kotlin DSL我是不太能理解的 像下列這一段

application {
    // Define the main class for the application.
    mainClassName = "tw.elliot.App"
}

我能知道這用意是指定main class,但語法上,我沒辦法解釋。 看了一段時間,才大致上理解

其實,要這樣看

project.application({
    mainClassName = "tw.elliot.App"
})

再來要說說我理解的過程

Function type 與 Lambda

要開始看kts設定前,一定要瞭解的是kotlin是有Function Type的

Function Type

  1. () -> Unit, 這Function Type沒有參數,也不回傳結果
  2. (String) -> Int, 這Function Type有一個String參數,回傳Int
  3. (String) -> () -> Int, 這Function Type有一個String參數,回傳一個會返回Int的Function

將變數設定為Function Type

var functionType1: () -> Unit

var functionType2: (String) -> Int

再來,定義Function Type實做

步驟1,利用匿名涵數

var functionType1: () -> Unit = fun() { println("IT'S FUNCTION TYPE 1!")}

var functionType2: (String) -> Int = fun(name:String): Int {
	println("IT'S FUNCTION TYPE 2 for ${name}!")
	return 1
}

步驟2,利用Lambda

var functionType1: () -> Unit = { println("IT'S FUNCTION TYPE 1!")}

var functionType2: (String) -> Int =  { name:String ->
	println("IT'S FUNCTION TYPE 2 for ${name}!")
	1
}

var functionType3: (String) -> Int =  { it ->
	println("IT'S FUNCTION TYPE 3 for ${it}!")
	1
}

var functionType4: (String) -> Int =  {
	println("IT'S FUNCTION TYPE 4 for ${it}!")
	1
}

在此functionType3 利用了Kotlin的特例,當lambda僅有一個參數時,可用it 取代,此時連型別都不用宣告了

functionType4裡,連it都省了....

透過步驟1跟2,可以瞭解 function到Lambda 簡化的過程,這部份在Kotlin DSL裡會一直使用

Receiver

當我們想為Class增加新Function,在Kotlin裡可以這樣做

fun String.internalNewFunc(): Unit {
	println("Test NewFunc with ${this} -- length [${this.length}]!")
}

此時的Receiver就是指String,在function中可用this取得receiver的實例。

上面的例子是為了String多了一個新的function,但用途不大,接下來我們可以配合function type,就可以有較明顯的作用

fun String.externalFuncPlug(funct: String.() -> Unit) {
	this.funct()
}

我們把function type當做參數,送到了String當中,大概可以這樣用

fun main() {
	var target: String = "Target"
  
	target.externalFuncPlug {
		println("Test NewFunc with ${this} -- length [${this.length}]!")
	}

	target.externalFuncPlug {
		target = this.toUpperCase()
	}
}

我們留下了一個口,讓新增的功能可以直接加進去,而不需要一直加寫新的function.

聰明如你,應該也想到了,如果將function type設為一個var 或 val,也能當參數傳入,不就更簡化了?

var exNewFunc: String.() -> Unit = {
	println("Test NewFunc with ${this} -- length [${this.length}]!");
}


fun main() {
	var target: String = "Target"

	target.externalFuncPlug(exNewFunc)
}

整理

fun String.internalNewFunc(): Unit {
	println("Test NewFunc with ${this} -- length [${this.length}]!")
}

fun String.externalFuncPlug(funct: String.() -> Unit) {
	this.funct()
}

var exNewFunc: String.() -> Unit = {
	println("Test NewFunc with ${this} -- length [${this.length}]!");
}

fun main() {

	var target: String = "Target"

	target.internalNewFunc()

	target.externalFuncPlug {
		println("Test NewFunc with ${this} -- length [${this.length}]!")
	}

	target.externalFuncPlug(exNewFunc)

}

執行結果都相同

Test NewFunc with Target -- length [6]!
Test NewFunc with Target -- length [6]!
Test NewFunc with Target -- length [6]!

再整理

kotlin當然想得到上面這個入口,所以直接定了一個apply()做為讓外部加入新function的入口

var exNewFunc: String.() -> Unit = {
    println("Test NewFunc with ${this} -- length [${this.length}]!");
}

fun main() {

	var target: String = "Target"
    
	target.apply(exNewFunc)
	
	//再簡化,去掉(),直接寫下要做的事
	target.apply {
		println("Test NewFunc with ${this} -- length [${this.length}]!")
	}

}

到這裡,才能看得懂DSL裡在做什麼

Project.application

實際翻下source code,果然發現這部份的DSL就是為了JavaApplication增加新增操作

/**
 * Configures the [application][org.gradle.api.plugins.JavaApplication] extension.
 */
fun org.gradle.api.Project.`application`(configure: org.gradle.api.plugins.JavaApplication.() -> Unit): Unit =
    (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("application", configure)

這方式,加上一段自訂的function,並讓我們能取得JavaApplication的變數, 而最初的plugins

plugins {
    java
    application
}

也就只是在為我們增加像上列application的切入點而已。

發表迴響

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料