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ù)f、g,返回另外一個(gè)函數(shù)。
而f和g這個(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)換方式),因此,g和f的參數(shù)與返回值類型不一定需要有顯示的關(guān)聯(lián)。此處不再細(xì)說。
而如果使用內(nèi)聯(lián)函數(shù)來定義,需要禁止f和g兩個(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))
}