[譯]Kotlin珍品 4 - 多是你沒見過的函數類型調用方式

翻譯說明: 翻譯水平有限,文章內可能會出現不許確甚至錯誤的理解,請多多包涵!歡迎批評和指正.每篇文章會結合本身的理解和例子,但願對你們學習Kotlin起到一點幫助.android

原文地址: [Kotlin Pearls 4] It’s an Object… It’s a Function… It’s an Invokable 編程

原文做者: Uberto Barbinibash

前言

在我開始去看其餘人寫的Kotlin代碼的時候,我當時在看到下面的class時很是震撼:ide

class ReceiptText(val template: String): (Int) -> String {
  override fun invoke(amount: Int): String =
        template.replace("%", amount.toString())
}
複製代碼

什麼?一個繼承函數類型的類!能夠這樣玩的麼?我當時嚴重懵逼了!同窗,若是你也有點懵了的話,那我以爲看完這篇文章就應該能搞(geng)懂(meng)了.函數式編程

正文

不要懵.函數類型跟Kotlin中其餘的類型一個尿性;你能夠把它們當參數,變量和其餘的用法,因此爲毛不能繼承它呢?先不過多糾結這個問題了啊,能夠繼承!可是若是你繼承了一個函數類型的話,編譯器會強制讓你重載一個跟函數類型裏面的參數和返回類型相對應的invoke方法.例如函數類型(Int) -> String那麼invoke就是invoke(xx:Int) : String函數

這個invoke方法是什麼呢?它就是個沒有名字的方法,因此咱們在這個類的對象的後面加上括號就能用了(若是有參數在括號裏面帶上對應類型的參數).學習

在函數式編程中,函數類型被稱爲箭頭類型,由於它們表達了從一種類型到另外一種類型的態射.像泛型同樣,它們屬於複合類型集合的一部分.ui

好了.目前來講這個知識量夠了.如今讓咱們看看能用函數類型作什麼了!spa

  • 類型別名翻譯

    在這個系列的前面的文章中我都刻意的避免使用這種類型別名的用法,就是爲了讓這個例子更能給你們加深印象.一般我我的是會常常在個人項目中使用類型別名的.

    typealias FInt2String = (Int) -> String
    
    class InheritFromFunctionTypes(val template: String) : FInt2String {
       override fun invoke(p1: Int): String {
           return template.replace("%", p1.toString())
       }
    
    }
    複製代碼

    做爲一個例子,假設咱們須要從一個收據郵件裏面打印金額信息.而且咱們把文案模版的配置交給用戶.

    首先咱們不用函數類型的方式寫一個兩個參數的方法:

    fun receipt(template: String, amount: Int) = 
        template.replace("%", amount.toString())
    複製代碼

    而後像這樣用起來:
    val text = receipt(readTemplateFromConf(), amount)

    這樣一看咱們不用函數類型的方式也能夠實現啊!可是你們想一下,這種方式的實現同時須要傳入文字配置範本和金額兩個參數.可是在實際場景中咱們只須要在啓動的時候獲取一次模版,而後在咱們讀取金額的時候再作打印操做.這裏若是用函數類型的方式,咱們能夠輕鬆的把讀取模版和金額的動做分開來作:

    class ReceiptText(val template: String): (Int) -> String {
        override fun invoke(amount: Int): String =
        template.replace("%", amount.toString())
    }
    val receipt = ReceiptText("Thank you for you donation of $%!")//模版
    //作些其餘的事情...
    val text = receipt(123) // 金額 
    
    //"Thank you for you donation of $123!"
    複製代碼
  • lambda表達式
    上面的例子還有一種解法就是用lambda去讓方法返回函數類型的實例.

    fun receiptText(template: String):(Int)->String ={
        amount -> template.replace("%", amount.toString())//經過lambda獲得函數類型的實例
    }
    
    val text2 = receiptText("Thank you for you donation of %!")
    
    
    println(text2(2))
    
    //"Thank you for you donation of 2!"
    
    
    複製代碼

    比較這兩種實現方式,我認爲,第一個基於類的,可讀性更強而且更適合複雜邏輯的支持.第二個屬於高階函數,更方便可是有可讀性有一點差,因此我傾向於僅僅在簡單的邏輯下使用.

  • 配合operator的使用
    objects 和 classes均可以有多個operator invoke方法.我發現它在處理密封類時尤爲有用.

    fun receiptText(template: String):(Int)->String ={
        amount -> template.replace("%", amount.toString())//經過lambda獲得函數類型的實例
    }
    
    sealed class TemplateString
    
    //an object with invoke operator
    object ReceiptTextObj : TemplateString() {
    operator fun invoke(amount: Int): String = receiptText("My receipt for $%")(amount)
    operator fun invoke(template: String, amount: Int): String = receiptText(template)(amount)
    }
    複製代碼

    注意,雖然object ReceiptTextObj沒有繼承咱們的函數類型(Int) -> String,不過它的invoke 方法跟咱們的類型一致.

  • 泛型類型
    方法類型能夠像普通類型同樣工做,那麼泛型類型呢?答案是 能夠的啊!

    拿集合泛型舉個例子.咱們能夠經過把上面三個列子放進一個集合,而後傳入不一樣的金額參數再去去調用:

    val functions = mutableListOf<(Int) -> String>()
    functions.add(receiptText("TA %"))//lambda獲取函數類型實例
    functions.add(ReceiptText("Thank you for $%!"))//繼承函數類型的實例類
    functions.add(ReceiptTextObj::invoke)//密封單例類的invoke
    val receipts = functions.mapIndexed{i, f -> f(i+100) }
    //["TA 100", "Thank you for $101!", "My receipt for $102"]
    複製代碼

    我很想知道你是如何使用它們的,以及其餘可能的用途。請你們在評論區留言或者給我私信.

  • Builder模式
    咱們能夠用它在建造者模式中構造一個複雜的對象:

    data class Person(val name: String, val age: Int, val weight: Double)
    val personBuilder: (String) -> (Int) -> (Double) -> Person = 
    { name ->
        { age ->
            { weight ->
                Person(name, age, weight)
            }
        }
    }
    複製代碼

    使用起來像這樣:

    val frank = personBuilder("Frank")(32)(78.5)
    //Person(name=Frank, age=32, weight=78.5)") val names = listOf("Joe", "Mary", "Bob", "Alice") val people: List<Person> = names .map { personBuilder(it) } //名字 .map { it(nextInt(80)) } //隨機年齡 .map { it(nextDouble(100.0)) } //隨機體重 //4 個隨機 Person 實例 複製代碼
  • DSL

    object Console {
        operator fun invoke (block: (String) -> String): Nothing {
            while (true) {
                val l = readLine()
                if (!l.isNullOrBlank())
                    println(block(l))
            }
        }
    }
    複製代碼

    上面這段代碼的邏輯是一個控制檯無限循環的讀取輸入流,而後把讀取內容交給一個函數類型去處理.能夠用到一個用戶輸入問題,而且控制檯返回答案的場景,就想這樣:

    fun main() {
        println ("Write your command!")
        Console {
            val parts = it.split(' ')
            when (parts[0]) {
                "go" -> "going ${parts[1]}"
                "eat" -> "eating ${parts[1]}"
                "quit" -> throw InterruptedException("Program has been terminated by user")
                else -> "I don't think so..."
            }
        }
    }
    複製代碼

結尾

我但願大家能喜歡這篇文章,若是喜歡的話 請關注原做者或者在掘金關注我並給我點個贊吧.

相關文章
相關標籤/搜索