Kotlin 核心编程 (30):基础语法 2.3.8

阅读数:1 2019 年 12 月 25 日 15:33

Kotlin核心编程(30):基础语法 2.3.8

(“柯里化”风格、扩展函数)

内容简介
本书不是一本简单介绍 Kotlin 语法应用的图书,而是一部专注于帮助读者深入理解 Kotlin 的设计理念,指导读者实现 Kotlin 高层次开发的实战型著作。书中深入介绍了 Kotlin 的核心语言特性、设计模式、函数式编程、异步开发等内容,并以 Android 和 Web 两个平台为背景,演示了 Kotlin 的实战应用。
全书共 13 章,分为 4 个部分:
热身篇—Kotlin 基础(第 1~2 章),简单介绍了 Kotlin 设计哲学、生态及基础语法,其中包括 Kotlin 与 Scala、Java 之间的关联与对比,以及 Kotlin 的类型声明的特殊性、val 和 var 的使用、高阶函数的使用、面向表达式编程的使用、字符串的定义与操作等内容;
下水篇—Kotlin 核心(第 3~8 章),深入介绍了面向对象、代数数据类型、模式匹配、类型系统、Lambda、集合、多态、扩展、元编程等 Kotlin 开发核心知识,这是本书的重点,其中涉及很多开发者特别关心的问题,比如多继承问题、模式匹配问题、用代数数据类型抽象业务问题、泛型问题、反射问题等。
潜入篇—Kotlin 探索(第 9~11 章),探索 Kotlin 在设计模式、函数式编程、异步和并发等编程领域的应用,其中包括对 4 大类设计模式、Typeclass 实现、函数式通用结构设计、类型替代异常处理、共享资源控制、CQRS 架构等重点内容的深入剖析;
遨游篇—Kotlin 实战(第 12~13 章),着重演示了 Kotlin 在 Android 和 Web 平台的实战案例,其中涉及架构方式、单向数据流模型、解耦视图导航、响应式编程、Spring 5 响应式框架和编程等内容。

我们已经知道,函数参数化是一种十分强大的特性,结合 Lambda 表达式,能在很大程度上提高语言的抽象和表达能力。接下来,我们再来了解下高阶函数在 Kotlin 中另一方面的表现,即一个函数返回另一个函数作为结果。

通过之前的介绍,相信你已经能很容易地理解什么是返回一个函数。还记得我们上面使用过的例子吗?

复制代码
fun foo(x: Int) = { y: Int -> x + y }

表达上非常简洁,其实它也可以等价于:

复制代码
fun foo(x: Int): (Int) -> Int {
return { y: Int -> x + y }
}

现在有了函数类型信息之后,可以很清晰地发现,执行 foo 函数之后,会返回另一个类型为 (Int) -> Int 的函数。

如果你看过一些介绍函数式编程的文章,可能听说过一种叫作“柯里化(Currying)”的语法,其实它就是函数作为返回值的一种典型的应用。

简单来说,柯里化指的是把接收多个参数的函数变换成一系列仅接收单一参数函数的过程,在返回最终结果值之前,前面的函数依次接收单个参数,然后返回下一个新的函数。

拿我们最熟悉的加法举例子,以下是多参数的版本:

复制代码
fun sum(x: Int, y: Int, z: Int) = x + y + z
sum(1, 2, 3)

如果我们把它按照柯里化的思路重新设计,那么最终就可以实现链式调用:

复制代码
fun sum(x: Int) = { y: Int ->
{ z: Int -> x + y + z }
}
sum(1)(2)(3)

你会发现,柯里化非常类似“击鼓传花”的游戏,游戏开始时有个暗号,第 1 个人将暗号进行演绎,紧接着第 2 个人演绎,依次类推,经过一系列加工之后,最后一个人揭晓谜底。在这个过程中:

  • 开始的暗号就是第 1 个参数;
  • 下个环节的演绎就是返回的函数;
  • 谜底就是柯里化后最终执行获得的结果。

可见柯里化是一个比较容易理解的概念,那么为什么会有柯里化呢?

柯里化与 Lambda 演算
我们说过,Lambda 演算是函数式语言的理论基础。在严格遵守这套理论的设计中,所有的函数都只能接收最多一个参数。为了解决多参数的问题,Haskell Curry 引入了柯里化这种方法。值得一提的是,这种技术也是根据他的名字来命名的——Currying,后续其他语言也以此来称呼它。

说到底,柯里化是为了简化 Lambda 演算理论中函数接收多参数而出现的,它简化了理论,将多元函数变成了一元。然而,在实际工程中,Kotlin 等语言并不存在这种问题,因为它们的函数都可以接收多个参数进行计算。那么,这是否意味着柯里化对我们而言,仅仅只有理论上的研究价值呢?虽然柯里化在工程中并没有大规模的应用,然而在某些情况下确实起到了某种奇效。

在我们之前介绍过的 Lambda 表达式中,还存在一种特殊的语法。如果一个函数只有一个参数,且该参数为函数类型,那么在调用该函数时,外面的括号就可以省略,就像这样子:

复制代码
fun omitParentheses(block: () -> Unit) {
block()
}
omitParentheses {
println("parentheses is omitted")
}

此外,如果参数不止一个,且最后一个参数为函数类型时,就可以采用类似柯里化风格的调用:

复制代码
fun curryingLike(content: String, block: (String) -> Unit) {
block(content)
}
curryingLike("looks like currying style") {
content ->
println(content)
}
// 运行结果
looks like currying style

它等价于以下的的调用方式:

复制代码
curryingLike("looks like currying style", {
content ->
println(content)
})

实际上,在 Scala 中我们就是通过柯里化的技术,实现以上简化语法的表达效果的。Kotlin 则直接在语言层面提供了这种语法糖,这个确实非常方便。然而需要注意的是,通过柯里化实现的方案,我们还可以分步调用参数,返回值是一个新的函数。

复制代码
curryingLike("looks like currying style")
// 运行报错
No value passed for parameter 'block'

然而,以上实现的 curryingLike 函数并不支持这样做,因为它终究只是 Kotlin 中的一种语法糖而已。它在函数调用形式上近似柯里化的效果,但却并不是柯里化。Kotlin 这样设计的目的是让我们采用最直观熟悉的套路,来替代柯里化实现这种简洁的语法。

Scala 中的 corresponds 方法是另一个典型的柯里化应用,用它可以比较两个序列是否在某个比对条件下相同。现在我们依靠 Kotlin 上面这种特殊的类柯里化语法特性,再来实现一个 Kotlin 版本。

首先,我们先穿插下 Kotlin 的另一项新特性——扩展函数,这是 Kotlin 中十分强大的功能,我们会在第 7 章中重点介绍。当前我们先简单了解下它的使用,因为 corresponds 方法需要借助它来实现。

简单来说,Kotlin 中的扩展函数允许我们在不修改已有类的前提下,给它增加新的方法。如代码清单 2-4 所示。

代码清单 2-4
复制代码
fun View.invisible() {
this.visibility = View.INVISIBLE
}

在这个例子中,类型 View 被称为接收者类型,this 对应的是这个类型所创建的接收者对象。this 可以被省略,就像这样子:

复制代码
fun View.invisible() {
visibility = View.INVISIBLE
}

我们给 Android 中的 View 类定义了一个 invisible 方法,之后 View 对象就可以直接调用该方法来隐藏视图。

复制代码
views.forEach { it.invisible() }

回到我们的 corresponds 方法,基于扩展函数的语法,我们就可以对 Array<T> 类型增加这个方法。由于 Kotlin 的特殊语法支持,我们还是采用了定义普通多参数函数的形式。

复制代码
fun <A, B> Array<A>.corresponds(that: Array<B>, p: (A, B) -> Boolean): Boolean {
val i = this.iterator()
val j = that.iterator()
while (i.hasNext() && j.hasNext()) {
if (!p(i.next(), j.next())) {
return false
}
}
return !i.hasNext() && !j.hasNext()
}

然后再用柯里化的风格进行调用,就显得非常直观:

复制代码
val a = arrayOf(1, 2, 3)
val b = arrayOf(2, 3, 4)
a.corresponds(b) { x, y -> x+1 == y } // true
a.corresponds(b) { x, y -> x+2 == y } // false

虽然本节讲述的是函数作为返回值的应用,然而由于 Kotlin 的特殊语法,我们可以在大部分场景下用它来替代柯里化的方案,显得更加方便。

Kotlin核心编程(30):基础语法 2.3.8

购书地址 https://item.jd.com/12519581.html?dist=jd

评论

发布