Kotlin 把高階函數(shù)玩出花來

fun <A, B, C> compose(f: (A) -> B, g: (B) -> C): (A) -> C = {
    g(f(it))
}
infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R = fun(a) = f(this(a))

這是誰?它想干啥?

先一個(gè)一個(gè)來看:

fun <A, B, C> compose(f: (A) -> B, g: (B) -> C): (A) -> C = {
    g(f(it))
}

對(duì)于compose函數(shù),接收兩個(gè)參數(shù)fg,返回另外一個(gè)函數(shù)。

fg這個(gè)兩個(gè)參數(shù)都是函數(shù)類型:

f函數(shù)接收一個(gè)A類型參數(shù),返回一個(gè)B類型;

g函數(shù)接收一個(gè)B類型參數(shù),返回一個(gè)C類型。

compose的返回值也是一個(gè)函數(shù)類型,接收一個(gè)A類型參數(shù),返回一個(gè)C類型。

compose的方法體被省略,因?yàn)槭且粋€(gè)表達(dá)式,可以使用"="賦值。而對(duì)于{g(f(it))}表達(dá)式,使用下面的方式應(yīng)更加清晰:

fun <A, B, C> compose(g: (A) -> B, f: (B) -> C): (A) -> C {
    return { a: A ->
        val b:B = g(a)
        val c:C = f(b)
        c
    }
}

compose函數(shù)體中:

1. 執(zhí)行`g`函數(shù)返回一個(gè)`B`類型的變量`b`,
2. 把`b`變量作為`f`函數(shù)的參數(shù)返回一個(gè)`C`類型的變量`c`
3. 再返回這個(gè)變量`c`。

整個(gè)方法做了兩件事:把A通過g函數(shù)轉(zhuǎn)換成B;在把B通過f函數(shù)轉(zhuǎn)換成C。

這就叫做組合函數(shù),把兩個(gè)函數(shù)組合起來,返回一個(gè)組合后的函數(shù)

當(dāng)函數(shù)g的返回值是函數(shù)f的參數(shù)時(shí),可以把這兩個(gè)函數(shù)組合成一個(gè)新的函數(shù)。這個(gè)新函數(shù)的參數(shù)是函數(shù)g的參數(shù),返回值是函數(shù)f的返回值。

最難理解的應(yīng)該是返回值是一個(gè)函數(shù),但是想想高階函數(shù)的定義,應(yīng)該就清晰多了。

當(dāng)然,這只是入門級(jí)的定義,我們可以在表達(dá)式中進(jìn)行任意的轉(zhuǎn)換(只要存在這種轉(zhuǎn)換方式),因此,gf的參數(shù)與返回值類型不一定需要有顯示的關(guān)聯(lián)。此處不再細(xì)說。

而如果使用內(nèi)聯(lián)函數(shù)來定義,需要禁止fg兩個(gè)函數(shù)的局部return功能:

inline fun <A, B, C> compose(crossinline f: (A) -> B, crossinline g: (B) -> C): (A) -> C = {
    g(f(it))
}

還可以使用java的方式來定義,以面向?qū)ο蟮乃季S來考慮,這種方試應(yīng)該比較容易理解:

public static <A, B, C> Function<A, C> compose(Function<A, B> f, Function<B, C> g) {
    return a -> g.apply(f.apply(a));
}

要怎么使用呢?

【案例】獲取一個(gè)字符的長度,然后判斷這個(gè)長度是否是偶數(shù)。

val result = compose(String::length) { it % 2 == 0 }("abcd")

compose函數(shù)返回的是一個(gè)函數(shù),因此我們可以執(zhí)行這個(gè)函數(shù)得到結(jié)果。

這種方式可能也比較難理解,或許拆開更容易理解:

val compose = compose<String, Int, Boolean>({ it.length }, { it % 2 == 0 })
val result = compose("abcd")

還可以使用函數(shù)引用的方式:

fun main() {
    fun even(a: Int) = a % 2 == 0
    val result = compose(String::length, ::even)("abcd")
}

對(duì)于andThen這個(gè)函數(shù)就更有意思了:

infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R = fun(a) = f(this(a))

它是一個(gè)擴(kuò)展函數(shù),還是(A)->B這個(gè)函數(shù)類型的擴(kuò)展,甚至還是一個(gè)中綴函數(shù)。

把它轉(zhuǎn)換成下面的方式應(yīng)該就清晰多了:

infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R {
    return { a: A ->
        val b: B = this(a)
        val r: R = f(b)
        r
    }
}

andThen返回一個(gè)(A)->R類型的函數(shù),在方法體中沒有做其它操作,直接返回了這個(gè)類型。

this(a)又是什么呢?

andThen(A)->B類型的函數(shù)的擴(kuò)展,因此方法體中的this就表示(A)->B這個(gè)函數(shù)對(duì)象。

this(a)就表示調(diào)用了(A)->B這個(gè)函數(shù)對(duì)象,它返回一個(gè)B類型的值b;

再把b代入?yún)?shù)f中返回一個(gè)R類型的r;

再把這個(gè)r返回。

整個(gè)執(zhí)行流程跟compose函數(shù)是一樣的,都是先執(zhí)行某個(gè)函數(shù),根據(jù)這個(gè)函數(shù)的返回值來執(zhí)行另一個(gè)函數(shù)。

compose函數(shù)跟andThen函數(shù)的作用也是一樣的,只是調(diào)用方式不一樣。

那它是怎么簡化來的呢?

最初的方式如下,它返回一個(gè)匿名函數(shù):

infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R {
    return fun(a: A): R {
        val b = this(a)
        return f(b)
    }
}

把這個(gè)局部函數(shù)簡化一下:

//1. 合并匿名函數(shù)體中的函數(shù)調(diào)用,這個(gè)匿名函數(shù)的參數(shù)類型也可以省略
infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R {
    return fun(a): R {
        return f(this(a))
    }
}

//2. 但表達(dá)式的函數(shù)體用=表示
infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R {
    return fun(a) = f(this(a))
}

此時(shí)andThen函數(shù)體還是一個(gè)單表達(dá)式,還能夠合并:

infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R = fun(a) = f(this(a))

甚至還可以使用返回類型推斷,但此時(shí)局部函數(shù)的參數(shù)類型不能省略:

infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R) = fun(a: A) = f(this(a))

還可以不用匿名函數(shù),使用lambda的形式,這看起來就跟compose一樣了:

inline infix fun <A, B, R> ((A) -> B).andThen(crossinline f: (B) -> R): (A) -> R = {
    f(this(it))
}

還是以【獲取一個(gè)字符的長度,然后判斷這個(gè)長度是否是偶數(shù)】為例:

fun main() {
     val result = { str: String -> str.length }.andThen{ length: Int -> length % 2 == 0 } ("abcd")
}

看起來不是很直觀,美化一下,把這兩個(gè)lambda提取出來:

fun main() {
    val length: (String) -> Int = { it.length }
    val even: (Int) -> Boolean = { it % 2 == 0 }
    val result = length.andThen(even)("abcd")
}

還可以使用中綴表達(dá)式的方式:

fun main() {
    val length: (String) -> Int = { it.length }
    val even: (Int) -> Boolean = { it % 2 == 0 }
    val result = (length andThen even)("abcd")
}

又或者使用局部函數(shù)來定義,然后使用函數(shù)引用的方式:

fun main() {
    fun length(s: String) = s.length
    fun even(i: Int) = i % 2 == 0
    val result = (::length andThen ::even)("abcd")
}

如果考慮讓菜鳥看不懂,還可以使用匿名函數(shù)的形式:

val result = ((fun(s: String) = s.length) andThen (fun(i: Int) = i % 2 == 0))("abcd")

可以看出,kotlin確實(shí)可以為所欲為。

在java中,也有andThen這個(gè)函數(shù),它被定義在Function接口中:

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (t) -> {
        return after.apply(this.apply(t));
    };
}

既然andThen都有了,那又怎么少的了beforeAnd呢?

infix fun <A, B, R> ((B) -> R).beforeAnd(f: (A) -> B): (A) -> R = {
    this(f(it))
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容