美文网首页
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