第5章 函数与函数式编程

凡此变数中函彼变数者,则此为彼之函数。 ( 李善兰《代数学》)

函数式编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以传入函数参数,也可以返回一个函数。函数式编程 (简称FP) 是一种编程范式(programming paradigm)。

函数式编程与命令式编程最大的不同是:函数式编程的焦点在数据的映射,命令式编程(imperative programming)的焦点是解决问题的步骤。函数式编程不仅仅指的是Lisp、Haskell、 Scala等之类的语言,更重要的是一种编程思维,解决问题的思考方式,也称面向函数编程。

函数式编程的本质是函数的组合。例如,我们想要过滤出一个List中的奇数,用Kotlin代码可以这样写

package com.easy.kotlinfun main(args: Array<String>) {val list = listOf(1, 2, 3, 4, 5, 6, 7)println(list.filter { it % 2 == 1 }) // lambda表达式
}

这个映射的过程可以使用下面的图来形象化地说明

filter 函数

而同样的逻辑我们使用命令式的思维方式来写的话,代码如下

package com.easy.kotlin;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;import static java.lang.System.out;public class FilterOddsDemo {public static void main(String[] args) {List<Integer> list = Arrays.asList(new Integer[] {1, 2, 3, 4, 5, 6, 7});out.println(filterOdds(list)); // 输出:[1, 3, 5, 7]}public static List<Integer> filterOdds(List<Integer> list) {List<Integer> result = new ArrayList();for (Integer i : list) {if (isOdd(i)) {result.add(i);}}return result;}private static boolean isOdd(Integer i) {return i % 2 != 0;}
}

我们可以看出,函数式编程是简单自然、直观易懂且美丽优雅的编程风格。函数式编程语言中通常都会提供常用的map、reduce、filter等基本函数,这些函数是对List、Map集合等基本数据结构的常用操作的高层次封装,就像一个更加智能好用的工具箱。

5.1 函数式编程简介

函数式编程是关于不变性和函数组合的编程范式。函数式编程有如下特征

  • 一等函数支持(first-class function):函数也是一种数据类型,可以当做参数传入另一个函数,同时一个函数也可以返回函数。
  • 纯函数(pure function)和不变性(immutable):纯函数指的是没有副作用的函数(函数不去改变外部的数据状态)。例如,一个编译器就是一个广义上的纯函数。在函数式编程中,倾向于使用纯函数编程。正因为纯函数不会去修改数据,同时又使用不可变数据,所以程序不会去修改一个已经存在的数据结构,而是根据一定的映射逻辑创建一份新的数据。函数式编程是去转换数据而非修改原始数据。
  • 函数的组合(compose function):在面向对象编程中,是通过对象之间发送消息来构建程序逻辑;而在函数式编程中,是通过不同函数的组合构建程序逻辑。

5.2 声明函数

Kotlin中使用 fun 关键字来声明函数,其语法实例如下图所示

Kotlin 声明函数

为了更加直观的感受到函数也可以当做变量来使用,我们声明一个函数类型的变量 sum 如下

>>> val sum = fun(x:Int, y:Int):Int { return x + y }
>>> sum
(kotlin.Int, kotlin.Int) -> kotlin.Int

我们可以看到这个函数变量 sum 的类型是

(kotlin.Int, kotlin.Int) -> kotlin.Int

这个带箭头( -> )的表达式就是一个函数类型,表示一个输入两个Int类型值,输出一个Int类型值的函数。我们可以直接使用这个函数字面值 sum

>>> sum(1,1)
2

从上面的这个典型的例子我们可以看出,Kotlin也是一门面向表达式的语言。既然 sum 是一个代表函数类型的变量,稍后我们将看到一个函数可以当做参数传入另一个函数中(高阶函数)。

当然,我们仍然可以像C/C++/Java中一样,直接带上函数名来声明一个函数

fun multiply(x: Int, y: Int): Int {return x * y
}multiply(2, 2) // 4

5.3 lambda表达式

我们在本章开头部分讲到了这段代码

val list = listOf(1, 2, 3, 4, 5, 6, 7)
list.filter { it % 2 == 1 }

这里的filter函数的入参 { it % 2 == 1 } 就是一段 lambda表达式。实际上,因为filter函数只有一个参数,所有括号被省略了。所以,filter函数调用的完整写法是

list.filter ({ it % 2 == 1 })

其中的filter函数声明如下

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>

其实,filter函数的入参是一个函数 predicate: (T) -> Boolean 。 实际上,

{ it % 2 == 1 }

是一种简写的语法,完整的lambda表达式是这样写的

{  it -> it % 2 == 1 }

如果拆开来写,就更加容易理解

>>> val isOdd = { it: Int -> it % 2 == 1 } // 直接使用lambda表达式声明一个函数,这个函数判断输入的Int是不是奇数
>>> isOdd
(kotlin.Int) -> kotlin.Boolean // isOdd函数的类型
>>> val list = listOf(1, 2, 3, 4, 5, 6, 7)
>>> list.filter(isOdd) // 直接传入isOdd函数
[1, 3, 5, 7]

5.4 高阶函数

其实,在上面的代码示例 list.filter(isOdd) 中,我们已经看到了高阶函数了。现在我们再添加一层映射逻辑。我们有一个字符串列表

val strList = listOf("a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg")

然后,我们想要过滤出字符串元素的长度是奇数的列表。我们把这个问题的解决逻辑拆成两个函数来组合实现

val f = fun (x: Int) = x % 2 == 1 // 判断输入的Int是否奇数
val g = fun (s: String) = s.length // 返回输入的字符串参数的长度

我们再使用函数 h 来封装 “字符串元素的长度是奇数” 这个逻辑,实现代码如下

val h = fun(g:  (String) -> Int, f: (Int) -> Boolean):  (String) -> Boolean {return { f(g(it)) }
}

但是,这个 h 函数的声明未免有点太长了。尤其是3个函数类型声明的箭头表达式,显得不够简洁。不过不用担心。

Kotlin中有简单好用的 Kotlin 类型别名, 我们使用 G,F,H 来声明3个函数类型

typealias G = (String) -> Int
typealias F = (Int) -> Boolean
typealias H = (String) -> Boolean

那么,我们的 h 函数就可简单优雅的写成下面这样了

val h = fun(g: G, f: F): H {return { f(g(it)) } // 需要注意的是,这里的 {} 是不能省略的
}

这个 h 函数的映射关系可用下图说明

h 函数的映射关系

函数体中的这句代码 return { f(g(it)) } , 这里的 {} 它代表这是一个lambda表达式,返回的是一个 (String) -> Boolean 函数类型。如果没有 { } , 那么返回值就是一个布尔类型Boolean了。

通过上面的代码例子,我们可以看到,在Kotlin中,我们可以简单优雅的实现高阶函数。OK,现在逻辑已经实现完了,下面我们在 main 函数中运行测试一下效果。

fun main(args: Array<String>) {val strList = listOf("a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg")println(strList.filter(h(g, f))) // 输出:[a, abc, abcde, abcdefg]
}

当你看到 h(g, f) 这样的复合函数的代码时,你一定很开心,感到很自然,这跟数学公式真是很贴近,简单易懂。

本章小结

在Kotlin中,支持函数作为一等公民。它支持高阶函数、lambda表达式等。我们不仅可以把函数当做普通变量一样传递、返回,还可以把它分配给变量、放进数据结构或者进行一般性的操作。在Kotlin中进行函数式编程相当简单自如。

第5章 函数与函数式编程相关推荐

  1. 《Kotlin项目实战开发》第5章 函数与函数式编程

    第5章 函数与函数式编程 凡此变数中函彼变数者,则此为彼之函数. ( 李善兰<代数学>) 函数式编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以传入函数 ...

  2. 《Kotlin项目实战开发》第1章 Kotlin是什么

    第1章 Kotlin是什么 当下互联网大数据云计算时代,数以百万计的应用程序在服务器.移动手机端上运行,其中的开发语言有很大一部分是用流行软件界20多年的.强大稳定的主力的编程语言Java编写. 如果 ...

  3. 《Kotlin项目实战开发》 第3章 类型系统与可空类型

    2019独角兽企业重金招聘Python工程师标准>>> 第3章 类型系统与可空类型 跟Java.C和C ++ 一样, Kotlin也是"静态类型编程语言". 通常 ...

  4. 《Python核心编程》第11章 函数和函数式编程 练习

    11-3 函数. 在这个练习中,我们将实现max()和min()内建函数. (a) 写分别带两个元素返回一个较大和较小元素,简单的max2()核min2()函数.他们应该可以用任意的python 对象 ...

  5. 第8章 泛型 《Kotlin 项目实战开发》

    第8章 泛型 通常情况的类和函数,我们只需要使用具体的类型即可:要么是基本类型,要么是自定义的类.但是在集合类的场景下,我们通常需要编写可以应用于多种类型的代码,我们最简单原始的做法是,针对每一种类型 ...

  6. Node项目实战开发-博客系统

    Nodejs项目实战开发-博客系统(已完结) 个人博客系统 欢迎访问我的博客~ MaXiaoYu's Bolg 前言: 开发技术 技术 版本 Node ^14.3.0 ejs ^3.1.3 expre ...

  7. [嵌入式Linux项目实战开发]基于QT4.7.4的音乐播放器实现与设计【2018年给力项目】

    [嵌入式Linux项目实战开发]基于QT4.7.4的音乐播放器实现与设计[2018年给力项目]是[创科之龙]团队aiku嵌入式视频教程系列制作的现有的音乐播放器. 主要功能实现: 1.新建工程,基类选 ...

  8. [嵌入式Linux项目实战开发]基于QT4.8的仓库管理系统实现功能【2019年给力项目】

    [嵌入式Linux项目实战开发]基于QT4.8的仓库管理系统实现功能[2019年给力项目] 支持导出 excel 表格 支持查看商品操作日志 支持高精度浮点运算 支持同一商品以不同价格入库 该软件已开 ...

  9. jsp项目开发案例_Laravel 中使用 swoole 项目实战开发案例一 (建立 swoole 和前端通信)life...

    1 开发需要环境 工欲善其事,必先利其器.在正式开发之前我们检查好需要安装的拓展,不要开发中发现这些问题,打断思路影响我们的开发效率. 安装 swoole 拓展包 安装 redis 拓展包 安装 la ...

最新文章

  1. Linux - 添加PATH环境变量
  2. JavaFX滚动事件
  3. SparkSQL 内置函数的使用(JAVA与Scala版本)
  4. Win8Metro(C#)数字图像处理--2.31灰度拉伸算法
  5. iOS-贝塞尔曲线之自定义饼图
  6. 微信公布9月朋友圈十大谣言:包括接通电话手机的钱就会被转走
  7. 小白用python处理excel文件-Python3操作Excel文件(读写)的简单实例
  8. uboot启动文件start.s和main.c解析
  9. meethigher-文库下载实现自动化
  10. 【高数】幂级数求和函数问题:用变限积分?积分下限是0?S(0)怎么求?求和时起始项n和角标有规定吗?
  11. KAKASI - 将日文转换为平假名/片假名/罗马音
  12. Windows系统镜像、PE系统下载地址大全
  13. 城市大数据及开放数据索引
  14. 湖南科技大学计算机实力强吗,湖南省这2所重点大学,吉首大学和湖南科技大学,谁的实力更强...
  15. 那些年我们用过的SSD
  16. 基础了解虚拟 DOM
  17. iOS category内部实现原理
  18. 图片大大了怎么修改大小KB不改变尺寸
  19. 刘同-《谁的青春不迷茫》
  20. IT培训有靠谱的机构吗,长什么样的?

热门文章

  1. Linux怎么把目录设置群组,linux设置目录和文件使用权限
  2. 【C语言基础】C语言异常捕获机制 - assert
  3. 服务器实际显示内存,服务器实际显示内存
  4. golang 读取文件最后一行_python3从零学习-5.4.3、文件输入流fileinput
  5. php读取excel的数据,php读取excel文件数据
  6. css 容器内 div 底部,CSS:在div容器的底部放置一個div容器
  7. android1.6,令人遗憾的Android 1.6系统_戴尔 Mini5(Streak)_手机其它OS-中关村在线
  8. matlab dct稀疏系数,Matlab DCT详解
  9. php 获取key的位置,PHP获取当前所在目录位置的方法
  10. Oracle/mysql联合查询union、union all