第5章 函数与函数式编程

第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中进行函数式编程相当简单自如。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/540498.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mcq 队列_人工智能能力问答中的人工智能概率推理(MCQ)

mcq 队列1) Which of the following correctly defines the use of probabilistic reasoning in AI systems? In situations of uncertainty, probabilistic theory can help us give an estimate of how much an event is likely to occur or happen.It helps to find the pr…

[转载] Python中的xrange和range的区别

参考链接&#xff1a; Python中的range()和xrange() 在python2 中 range(start,end,step)返回一个列表&#xff0c;返回的结果是可迭代对象&#xff0c;但不是迭代器。iter()转化为列表迭代器。xrange()返回的是一个序列&#xff0c;他也是可迭代对象&#xff0c;但不是迭代…

Kubernetes基础组件概述

本文讲的是Kubernetes基础组件概述【编者的话】最近总有同学问Kubernetes中的各个组件的相关问题&#xff0c;其实这些概念内容在官方文档中都有&#xff0c;奈何我们有些同学可能英文不好&#xff0c;又或者懒得去看&#xff0c;又或者没有找到&#xff0c;今天有时间就专门写…

c语言将链表写入二进制文件_通过逐级遍历将二进制树转换为单链表的C程序

c语言将链表写入二进制文件Problem statement: Write a C program to convert a binary tree into a single linked list by traversing level-wise. 问题陈述&#xff1a;编写一个C程序&#xff0c;通过逐级遍历将二进制树转换为单个链表 。 Example: 例&#xff1a; The ab…

[转载] C Primer Plus 第6章 C控制语句 6.16 编程练习及答案

参考链接&#xff1a; 用Python打印金字塔图案的程序 2019独角兽企业重金招聘Python工程师标准>>> 1、编写一个程序&#xff0c;创建一个具有26个元素的数组&#xff0c;并在其中存储26个小写字母。并让该程序显示该数组的内容。 #include int main (void) { …

C# String和string的区别

C#中同时存在String与string MSDN中对string的说明&#xff1a; string is an alias for String in the .NET Framework。string是String的别名而已&#xff0c;string是c#中的类&#xff0c;String是Framework的类&#xff0c;C# string 映射为 Framework的 String。如果用str…

要求用户在Python中输入整数| 限制用户仅输入整数值

input() function can be used for the input, but it reads the value as a string, then we can use the int() function to convert string value to an integer. input()函数可用于输入&#xff0c;但它将值读取为字符串&#xff0c;然后可以使用int()函数将字符串值转换为…

[转载] python——if语句、逻辑运算符号

参考链接&#xff1a; 用Python链接比较运算符 1.if条件判断语句&#xff1a; if 要判断的条件(True): 条件成立的时候&#xff0c;要做的事情 elif 要判断的条件(True): .... elif 要判断的条件(True): .... else: 条件不成立的时候要做的事情 示例&#xff1a; 判断学生…

洛谷 P2689 东南西北【模拟/搜索】

题目描述 给出起点和终点的坐标及接下来T个时刻的风向(东南西北)&#xff0c;每次可以选择顺风偏移1个单位或者停在原地。求到达终点的最少时间。 如果无法偏移至终点&#xff0c;输出“-1”。 输入输出格式 输入格式&#xff1a; 第一行两个正整数x1,y1&#xff0c;表示小明所…

单链表遍历_单链表及其遍历实现的基本操作

单链表遍历单链表 (Single linked list) Single linked list contains a number of nodes where each node has a data field and a pointer to next node. The link of the last node is to NULL, indicates end of list. 单个链表包含许多节点&#xff0c;其中每个节点都有一…

[转载] python中for语句用法_详解Python中for循环的使用_python

参考链接&#xff1a; 在Python中将else条件语句与for循环一起使用 这篇文章主要介绍了Python中for循环的使用,来自于IBM官方网站技术文档,需要的朋友可以参考下 for 循环 本系列前面 “探索 Python&#xff0c;第 5 部分&#xff1a;用 Python 编程” 一文讨论了 if 语句和…

windows 软链接的建立及删除

在windows服务器上有时有这样的需求&#xff0c;你的文件在f:\test中&#xff0c;但由于其它原因用户访问的是e:\test&#xff0c;如果又希望e:\test 中的文件与f:\test的保持同步&#xff0c;除了用同步软件来做外&#xff0c;可以用windows 的文件夹映射来做 cmd: mklink /J …

8086简单的指令流水线_在8086微处理器中执行流水线的指令和概念的步骤

8086简单的指令流水线Any computer or machine works according to some instructions. These instructions are responsible for all the work that the machine does. But how does a machine work to understand and execute that instruction? 任何计算机或机器都按照某些…

[转载] 使用Python编写打字训练小程序

参考链接&#xff1a; 在Python中切换大小写(替换) 你眼中的程序猿 别人眼中的程序猿&#xff0c;是什么样子&#xff1f;打字如飞&#xff0c;各种炫酷的页面切换&#xff0c;一个个好似黑客般的网站破解。可现实呢&#xff1f; 二指禅的敲键盘&#xff0c;写一行代码&#…

shell两个数字相乘_使用8086微处理器将两个16位数字相乘而不带进位

shell两个数字相乘Problem statement: 问题陈述&#xff1a; To perform multiplication operation between 2 16bit numbers with carry using 8086 Microprocessor. 使用8086微处理器在2个16位数字之间进行带进位的乘法运算。 Algorithm: 算法&#xff1a; Load the first…

Dwr 框架简单实例

Dwr 是一个 Java 开源库&#xff0c;帮助你实现Ajax网站。 它可以让你在浏览器中的Javascript代码调用Web服务器上的Java&#xff0c;就像在Java代码就在浏览器中一样。 Dwr 主要包括两部分&#xff1a; 在服务器上运行的 Servlet 来处理请求并把结果返回浏览器。 运行在浏览器…

[转载] Python进阶:设计模式之迭代器模式

参考链接&#xff1a; Python中的迭代器 在软件开发领域中&#xff0c;人们经常会用到这一个概念——“设计模式”&#xff08;design pattern&#xff09;&#xff0c;它是一种针对软件设计的共性问题而提出的解决方案。在一本圣经级的书籍《设计模式&#xff1a;可复用面向对…

JavaScript | 如何为变量分配十进制,八进制和十六进制值?

Just like C programming language, we can assign integer value in the different format to the variable. 就像C编程语言一样 &#xff0c;我们可以将不同格式的整数值分配给变量。 Assigning decimal value: It can be assigned simply without using any prefix. 分配十…

路由器DHCP和DHCP中继的配置

路由器 DHCP和DHCP中继的配置 路由器作为DHCP服务器&#xff1a; 1.配置router的地址&#xff1a;Route(config)# hostname gateway (更改主机名字) Gateway(config)# interface gigabitethernet 0/0 …

[转载] 大数据分析Python For循环教程

参考链接&#xff1a; Python中的迭代器函数1 大数据分析Python除了循环遍历列表之外&#xff0c;for循环还有很多其他功能&#xff0c;在现实世界的数据科学工作中&#xff0c;您可能需要将numpy数组和pandas DataFrames用于其他数据结构的循环。 大数据分析Python For循环教…