Kotlin开发笔记:函数式编程

news2024/10/7 14:26:25

Kotlin开发笔记:函数式编程

在这里插入图片描述

什么是函数式编程

简单来说,我们之前接触到的编程的主流就是命令式编程,我们需要告诉计算机做什么和如何做。而函数式编程的意思就是我们只需要告诉计算机我们想做什么,计算机会帮助我们实现如何做。我们可以看看维基百科的介绍:

在函数式编程中,函数是头等对象即头等函数,这意味着一个函数,既可以作为其它函数的输入参数值,也可以从函数中返回值,被修改或者被分配给一个变量。λ演算是这种范型最重要的基础,λ演算的函数可以接受函数作为输入参数和输出返回值。

实际上就是让我们使用封装的函数来进行编程而不需要每时每刻都关心细节的实现,只有我们想要的时候才关注细节。这种函数式编程的方式本质上是声明式的,它与命令式的编程各有优劣。不过在这个声明式编程兴起的时代,我们还是需要学习函数式编程的思想。

Lambda表达式

作为函数式编程中最重要的一个部分,我们需要了解什么是Lambda表达式。Lambda表达式是没有名称的函数,其返回类型是推断的。

通常我们的函数有四个部分:名称,返回类型,参数列表和主体,Lambda只保留函数最重要的部分–参数列表和主体,在Kotlin中一般是这样表示的:

{parameter list -> body}

Lambda被包含在{ }中。使用连字符箭头(->)把主体与参数列表分开,主体通常是单个语句或者表达式,当然也可以是多行。

书中给出了一个小提示:将lambda作为参数传递给函数时,除非它是最后一个参数。否则不要急于创建多行lambda。这将不利于我们阅读代码

一个小例子🌰

这里我们可以先演示一个简单的例子,比如说我们想要实现一个判断数是否为质数的函数,我们就可以这样写:

fun isPrime(n:Int):Boolean = n > 1 && (2 until n).none{i:Int -> n % i == 0}

这段代码的难以理解的点应该就是2 until n后面跟着的none函数了,就如同名字一样,none函数时判断范围内里有没有符合none接收的lambda表达式条件的数,一旦有一个符合条件,那么就返回false,否则返回true。

在这段代码内部我们可以吧n视为一个标量,而把i看做是一个变量,这样便于我们理解。一旦2到n-1的范围内没有一个数可以将n整除,那么n就是质数。

除此之外,Kotlin的类型推断可以简化我们的写法,我们不必显式指定i的类型,简化后的写法:

fun isPrime(n:Int):Boolean = n > 1 && (2 until n).none{i -> n % i == 0}

隐式参数

如果传递给参数的lambda只接受一个参数,那么我们可以省略参数声明用一个it来代替:

fun isPrime(n:Int):Boolean = n > 1 && (2 until n).none{ n % it == 0}

这里的it代表的就是范围内的每一个数。对于只带有一个参数的短lambda,我们可以省略参数声明和箭头->,并将it用于变量名。但是缺点是我们就不能很快地分辨一个lambda是不是有参数。对于长的lambda表达式来说,我们应该避免这种情况。

接收lambda

上面的none函数就是一个可以接收lambda表达式的函数,那么我们要怎么写一个接收lambda表达式的函数呢?我们继续以一个例子来介绍:

fun walkTo(action: (Int) -> Unit,n:Int) {
    (1..n).forEach{action(it)}
}

第一个参数就是一个lambda表达式,实际上也可以理解为接受一个函数。这个action参数接受一个参数列表为一个Int,无返回值的lambda表达式。对传入的lambda表达式进行调用也很简单,像函数一样使用它就好。

我们在主函数中调用一下:

fun main() {
    walkTo({ println("$it is working")},5)
}

输出是正常的:
在这里插入图片描述

将lambda表达式放在最后一个参数

上面的示例中我们将lambda表达式放在了参数列表的第一个位置上,这是个不好的习惯,这将不利于我们阅读代码。并且,将lambda表达式将允许我们简化一下函数的调用,我们先修改方法:

fun walkTo(n:Int,action: (Int) -> Unit) =
    (1..n).forEach{action(it)}

现在,我们调用函数应该是这样的:

 walkTo(5,{println("$it is working")})

但是我们此时也可以这样调用:

walkTo(5){ println("$it is working")}

对于多个参数,只需要将lambda表达书放在最后即可:

fun main() { 
    walkTo(5,5){println("$it is working")}
}

fun walkTo(n:Int,x:Int,action: (Int) -> Unit) =
    (1..n).forEach{action(it)}

返回lambda表达式的函数

在Kotlin中我们还可以返回lambda表达式,比如我们可以在一个列表中查找指定字符长度的字符串,我们就可以用find函数接受一个lambda表达式,但是每次都重新写lambda表达式显然是令人厌烦的,我们可以创建一个函数,这个函数就专门用来返回lambda表达式:

fun searcgLength(n:Int):(String) -> Boolean {
    return {name:String -> name.length == n}
}

这样我们就可以在主函数中通过这个函数来调用:

fun main() {
    val names = listOf("aa","bbb","cccc","ddddd")
    println(names.find(searcgLength(4)))
}

不过需要说明的是返回的lambda表达式本质上是函数的签名。它并不会自动执行函数。比如:

fun main() {
    {println("jack")}
}

中间花括号这一对就是一个lambda表达式,但是它并不会自动打印内容,这样写可能更清晰。

fun main() {
    val hanshu = {println("jack")}
    hanshu() // 或者hanshu.invoke()
}

这样才会打印出jack。

lambda和匿名函数

匿名函数和lambda表达式在本质上可能是一种东西,都是函数的签名,或者说是指向函数的起始地址(可以这么理解),和lambda表达式一样,匿名函数也可以用变量存储起来,比如说以下两种形式:

    val fn1 = fun(name:String):Boolean{return name.length == 5}
    val fn2 = {name:String -> name.length == 5}

这两种都可以传入我们之前写的find函数之中,在这里我们也可以看出Kotlin中匿名函数的写法,其实就是省略了函数名的函数定义写法。

闭包和词法作用域

这里首先需要介绍闭包的概念:

闭包(Closure)是一个编程概念,指的是一个包含了函数及其相关引用环境(变量、状态)的组合。换句话说,闭包是一个函数及其能够访问的其外部作用域中的变量集合。闭包允许函数捕获其声明时的上下文,并在稍后的时间内引用这些变量,即使函数是在其原始上下文之外执行的。

闭包通常用于实现函数式编程的特性,例如将函数作为参数传递、返回函数作为结果等。

如果一个lambda想要依赖外部条件,那么这类lambda就被称为闭包–这是因为它关闭了定义范围来绑定到非局部的属性和方法。 我们可以模拟闭包的情景:

fun main() {
    val factor = 2
    val doubleIt = {e:Int -> e * factor}
}

可以看到,lambda表达式中的factor变量是定义在lambda表达式之外的。在lambda表达式之内遇到factor变量,编译器就将在定义的范围内查找这个factor变量是在哪里定义的,这称之为词法作用域。比如这个示例中,lambda中的factor变量就被绑定到了前一行的val factor = 2上。

非局部和带标签的return

默认情况下,lambda表达式是不允许有return关键字的,这也是其和匿名函数的一个重要区别,即使匿名函数需要返回一个值,那么匿名函数必须要有return关键字。不过在某些情况下,lambda表达式也可以使用return。

借用书中的例子,先来创建一个invokeWith方法,接受一个Int参数和一个lambda表达式:

fun invokeWith(n:Int,action: (Int) -> Unit){
    println("enter invoke with$n")
    action(n)
    println("left invoke with$n")
}

接下来我们再创建一个caller函数,在这个caller函数中调用invokeWith函数:

fun caller(){
    (1..3).forEach { i ->
        invokeWith(i){
            println("enter for $it")

            if(it == 2){
                return
            }

            println("exit for $it")
        }
    }
    println("end of caller")
}

但是编译器会报错,它不允许我们用return关键字立即返回,原因是kotlin编译器不清楚我们到底要从哪个lambda中退出,究竟是invokeWith后面的lambda还是foreach后面的lambda,还是退出整个caller函数。

带标签的return

Kotlin在默认情况下是不允许我们使用return的,不过有两种例外:带标签的return和非局部return。我们先来介绍带标签的return。这个和C中的jumpTo有点像,简而言之就是指定return跳出的lambda表达式:

fun invokeWith(n:Int,action: (Int) -> Unit){
    println("enter invoke with$n")
    action(n)
    println("left invoke with$n")
}

fun caller(){
    (1..3).forEach { i ->
        invokeWith(i) here@{
            println("enter for $it")

            if(it == 2){
                return@here
            }

            println("exit for $it")
        }
    }
    println("end of caller")
}

这里我们将invokeWith后面的lambda块标记为here标签,并在后面的return中使用,这样return跳出时就会跳过整个invokeWith块,我们来看运行结果:
在这里插入图片描述
可以看到并没有打印exit for 2,说明跳过了这一段。这个here标签是我们自己定义的,实际上也可以直接使用函数名,比如:

fun caller(){
    (1..3).forEach { i ->
        invokeWith(i) {
            println("enter for $it")

            if(it == 2){
                return@invokeWith
            }

            println("exit for $it")
        }
    }
    println("end of caller")
}

这里需要说明的是编译器不允许使用带标签的return返回到任意的外部范围–你只能从当前所包含的lambda返回,比如这样返回就是不行的:
在这里插入图片描述
我们不能返回到外部的lambda中。

非局部return

前面提到了Kotlin中默认是不允许我们在lambda中使用return的,除了带标签的return之外,还有一种情况也可以允许我们使用return,那就是使用内联函数。如果一个内联函数接受一个lambda表达式,那么在这个lambda表达式中也可以使用return,我们修改之前的例子:

fun caller(){
    (1..3).forEach { i ->
        if (i == 2){return}
        invokeWith(i) {
            println("enter for $it")

            if(it == 2){
                return@invokeWith
            }

            println("exit for $it")
        }
    }
    println("end of caller")
}

当遍历到i==2的情况时将直接调用return退出整个caller函数,查看输出:
在这里插入图片描述
这里可以在foreach的lambda之中调用return就是因为foreach是内联函数:

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

用inline标记为内联的函数中接收的lambda表达式之中就可以使用return,这就称之为非局部return。让我们来总结一下return的行为:

  • 默认情况下,在lambda中不允许使用return
  • 可以使用带标签的return跳出当前的lambda表达式
  • 只有当接收lambda的函数标记为内联的时候才可以使用非局部return来跳出整个函数
  • 如果我们可以使用非局部return从lambda中退出时,请记住这是从整个正在定义函数的函数之中退出的

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

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

相关文章

ubuntu20搭建环境使用的一下指令

1.更新源 sudo vim etc/apt/sources.listdeb http://mirrors.aliyun.com/ubuntu/ xenial main deb-src http://mirrors.aliyun.com/ubuntu/ xenial maindeb http://mirrors.aliyun.com/ubuntu/ xenial-updates main deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates…

小素数,大智慧

小素数&#xff0c;大智慧 定义判断方法方法1方法2方法3方法4方法5方法6方法7 定义 素数&#xff08;质数&#xff09;&#xff1a;在大于 1 的自然数中&#xff0c;只有 1 和该数本身两个因数的数 素数&#xff08;质数&#xff09;&#xff1a;在大于1的自然数中&#xff0c;…

No114.精选前端面试题,享受每天的挑战和学习

文章目录 vue3中的ref、toRef、toRefs说明下TS的优缺点说下函数式组件说下函数式编程 vue3中的ref、toRef、toRefs 下面是对Vue 3中的ref、toRef和toRefs进行比较的表格&#xff1a; reftoReftoRefs参数类型值类型或引用类型响应式对象响应式对象返回值Ref 对象Ref 对象响应式…

简单认识Docker的资源控制

文章目录 一、CPU资源限制1.设置CPU使用率上限2.设置CPU资源占用比&#xff08;设置多个容器才有效&#xff09;3.设置容器与CPU绑核 二、内存资源限制三、对磁盘I/O配额的限制 一、CPU资源限制 1.设置CPU使用率上限 Linux通过CFS&#xff08;Completely Fair Scheduler&#…

【简单认识Docker基本管理】

文章目录 一、Docker概述1、定义2.容器化流行的原因3.Docker和虚拟机的区别4.Docker核心概念 二、安装docker三、镜像管理1.搜索镜像2.下载&#xff08;拉取&#xff09;镜像3.查看已下载镜像4.查看镜像详细信息5.修改镜像标签6.删除镜像7.导出镜像文件和拉取本地镜像文件8.上传…

如何在Linux系统上搭建自己的FRP内网穿透

前言 我有一个1核1G的服务器有公网IP但是这个1核1G的服务器太垃圾了,几乎什么都跑不起来,不过网速还行,那我本地还有一个物理主机是一个4核4G的,那我就可以把这台主机安装上linux系统当成一个服务器来使用,然后把网络代理到公网IP上.使用内网穿透这篇文章也就出现了. FRP简介 F…

服务器CPU飚高排查

排查思路 当正在运行的Java服务导致服务器的CPU突然飙高时&#xff0c;我们该如何排查定位到哪个接口的哪行代码导致CPU飙高的问题呢&#xff1f;我主要提供两个方案&#xff1a; jstackarthas 准备工作 代码准备 现在需要准备一段可以让服务器CPU飙高的代码以及把代码部署…

鑫达惠购系统APP开发的功能架构介绍

鑫达惠购是一款新电商模式的购物分销系统&#xff0c;基于分销裂变的商业价值行为&#xff0c;快速地分享邀请用户注册。这个系统的模式有个特别的亮点&#xff0c;基于全网公排的模式快速推动用户在商城上的购买活动。 鑫达惠购客户端系统功能 包含的功能有&#xff1a;商城模…

memset の 那些事儿 (C++)

如果你在编程时开了一个数组 int a[100010] 这是后你想把他全部赋值为一个很大的值&#xff08;可能你用它来取min&#xff09; 这时候&#xff0c;应该这样写 for (int i 0; i < 100010; i ) a[i] 0x3f3f3f3f //0x3f3f3f3f 是一个比较接近int_max的一个数&#xff0…

机器学习笔记 - 使用VGG16深度学习模型进行图像相似度比较

一、简述 VGG16 是一个强大的预训练模型,可用于识别图像之间的相似性。通过使用该模型,我们可以从不同图像中提取高级特征并进行比较以识别相似性。该技术具有广泛的应用,从图像搜索和推荐系统到安全和监控。 在本文中,将利用该模型来查找两个图像之间的相似性。 …

Win11游戏高性能模式怎么开

1、点击桌面任务栏上的“开始”图标&#xff0c;在打开的应用中&#xff0c;点击“设置”&#xff1b; 2、“设置”窗口&#xff0c;左侧找到“游戏”选项&#xff0c;在右侧的选项中&#xff0c;找到并点击打开“游戏模式”&#xff1b; 3、打开的“游戏模式”中&#xff0c;找…

【汇编语言】栈及栈操作的实现

文章目录 栈结构栈操作栈的小结 栈结构 栈是一种只能在一端插入或删除的数据结构&#xff1b;栈有两个基本的操作&#xff1a;入栈和出栈&#xff1b; 入栈&#xff1a;将一个新的元素放到栈顶&#xff1b;出栈&#xff1a;从栈顶取出一个元素&#xff1b; 栈的操作规则&#…

单晶高温合金收获阶段性应用成果,科研人员已开展定向凝固实验

根据央视报道&#xff0c;中国科学院金属研究所的科研人员对从太空带回的铝硅合金样品进行了初步的解剖分析工作&#xff0c;并计划用不同的实验方法和合金材料继续开展相关实验工作&#xff0c;以进一步探索重力在单晶高温合金凝固过程中的具体作用及相关机理。 值得关注的是&…

ORB-SLAM2学习笔记9之图像帧Frame

先占坑&#xff0c;明天再完善… 文章目录 0 引言1 Frame类1.1 成员函数1.2 成员变量 2 Frame类的用途 0 引言 ORB-SLAM2学习笔记8详细了解了图像特征点提取和描述子的生成&#xff0c;本文在此基础上&#xff0c;继续学习ORB-SLAM2中的图像帧&#xff0c;也就是Frame类&#…

自我管理篇--工作做完了,我能不能到点就下班

以上简历模板资源的排版可能不是最优&#xff0c;但工作经历可以借鉴 工作做完了&#xff0c;我能不能到点就下班&#xff1f; 答案&#xff1a;是&#xff0c;每个人都是自由的 ​ 工作完了&#xff0c;我能不能准点下班&#xff0c;背后真正的问题是:你有没有找到那件让你愿意…

【算法——双指针】LeetCode 11 盛最多水的容器

题目描述&#xff1a; 解题思路&#xff1a; 如图所示&#xff1a; 1、我们考虑相距最远的两个柱子所能容纳水的面积。宽度是两根柱子之间的距离8&#xff1b;高度取决于两根柱子之间较短的那个&#xff0c;即左边柱子的高度3。水的面积就是3824。 2、如果选择固定一根柱子&…

【Linux】传输层协议:UDP和TCP

争做西格玛男人 文章目录 一、UDP协议1.端口号2.理解UDP报头3.UDP的特点&#xff08;面向数据报&#xff0c;全双工&#xff09; 二、TCP协议1.理解TCP报头某些TCP的策略1.1 TCP报头字段&#xff08;TCP的黏包问题&#xff09;1.2 网络协议栈和linux系统的联系&#xff08;以p…

SpringBoot + MyBatis-Plus构建树形结构的几种方式

1. 树形结构 树形结构&#xff0c;是指&#xff1a;数据元素之间的关系像一颗树的数据结构。由树根延伸出多个树杈 它具有以下特点&#xff1a; 每个节点都只有有限个子节点或无子节点&#xff1b;没有父节点的节点称为根节点&#xff1b;每一个非根节点有且只有一个父节点&a…

2023国赛数学建模A题思路模型代码汇总 高教社杯

本次比赛我们将会全程更新思路模型及代码&#xff0c;大家查看文末名片获取 之前国赛相关的资料和助攻可以查看 2022数学建模国赛C题思路分析_2022国赛c题matlab_UST数模社_的博客-CSDN博客 2022国赛数学建模A题B题C题D题资料思路汇总 高教社杯_2022国赛c题matlab_UST数模社…

econml介绍

EconML简介 EconML: A Python Package for ML-Based Heterogeneous Treatment Effects Estimation EconML是一个通过机器学习方法从观察数据中估计heterogeneous treatment effects的Python包。该软件包是微软研究院ALICE项目的一部分&#xff0c;目的是将最新的机器学习方法…