美文网首页
Kotlin 把高阶函数玩出花来

Kotlin 把高阶函数玩出花来

作者: 最爱大头猫 | 来源:发表于2021-11-12 13:31 被阅读0次
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))

这是谁?它想干啥?

先一个一个来看:

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

对于compose函数,接收两个参数fg,返回另外一个函数。

fg这个两个参数都是函数类型:

f函数接收一个A类型参数,返回一个B类型;

g函数接收一个B类型参数,返回一个C类型。

compose的返回值也是一个函数类型,接收一个A类型参数,返回一个C类型。

compose的方法体被省略,因为是一个表达式,可以使用"="赋值。而对于{g(f(it))}表达式,使用下面的方式应更加清晰:

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函数体中:

1. 执行`g`函数返回一个`B`类型的变量`b`,
2. 把`b`变量作为`f`函数的参数返回一个`C`类型的变量`c`
3. 再返回这个变量`c`。

整个方法做了两件事:把A通过g函数转换成B;在把B通过f函数转换成C

这就叫做组合函数,把两个函数组合起来,返回一个组合后的函数

当函数g的返回值是函数f的参数时,可以把这两个函数组合成一个新的函数。这个新函数的参数是函数g的参数,返回值是函数f的返回值。

最难理解的应该是返回值是一个函数,但是想想高阶函数的定义,应该就清晰多了。

当然,这只是入门级的定义,我们可以在表达式中进行任意的转换(只要存在这种转换方式),因此,gf的参数与返回值类型不一定需要有显示的关联。此处不再细说。

而如果使用内联函数来定义,需要禁止fg两个函数的局部return功能:

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

还可以使用java的方式来定义,以面向对象的思维来考虑,这种方试应该比较容易理解:

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

要怎么使用呢?

【案例】获取一个字符的长度,然后判断这个长度是否是偶数。

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

compose函数返回的是一个函数,因此我们可以执行这个函数得到结果。

这种方式可能也比较难理解,或许拆开更容易理解:

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

还可以使用函数引用的方式:

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

对于andThen这个函数就更有意思了:

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

它是一个扩展函数,还是(A)->B这个函数类型的扩展,甚至还是一个中缀函数。

把它转换成下面的方式应该就清晰多了:

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返回一个(A)->R类型的函数,在方法体中没有做其它操作,直接返回了这个类型。

this(a)又是什么呢?

andThen(A)->B类型的函数的扩展,因此方法体中的this就表示(A)->B这个函数对象。

this(a)就表示调用了(A)->B这个函数对象,它返回一个B类型的值b

再把b代入参数f中返回一个R类型的r;

再把这个r返回。

整个执行流程跟compose函数是一样的,都是先执行某个函数,根据这个函数的返回值来执行另一个函数。

compose函数跟andThen函数的作用也是一样的,只是调用方式不一样。

那它是怎么简化来的呢?

最初的方式如下,它返回一个匿名函数:

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)
    }
}

把这个局部函数简化一下:

//1. 合并匿名函数体中的函数调用,这个匿名函数的参数类型也可以省略
infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R {
    return fun(a): R {
        return f(this(a))
    }
}

//2. 但表达式的函数体用=表示
infix fun <A, B, R> ((A) -> B).andThen(f: (B) -> R): (A) -> R {
    return fun(a) = f(this(a))
}

此时andThen函数体还是一个单表达式,还能够合并:

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

甚至还可以使用返回类型推断,但此时局部函数的参数类型不能省略:

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

还可以不用匿名函数,使用lambda的形式,这看起来就跟compose一样了:

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

还是以【获取一个字符的长度,然后判断这个长度是否是偶数】为例:

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

看起来不是很直观,美化一下,把这两个lambda提取出来:

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

还可以使用中缀表达式的方式:

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

又或者使用局部函数来定义,然后使用函数引用的方式:

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

如果考虑让菜鸟看不懂,还可以使用匿名函数的形式:

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

可以看出,kotlin确实可以为所欲为。

在java中,也有andThen这个函数,它被定义在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))
}

相关文章

网友评论

      本文标题:Kotlin 把高阶函数玩出花来

      本文链接:https://www.haomeiwen.com/subject/zjonzltx.html