Scala中的函数高级使用
1.偏函数
1.基本介绍
- 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择
- 将包在大括号内的一组case语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出范围的值会忽略(未必会忽略,这取决于你打算怎样处理)
- 偏函数在Scala中是一个特质PartialFunction
2.快速入门
给定集合val list = List(1, 2, 3, 4, "abc") ,要求将集合list中的所有数字+1,并返回一个新的集合,要求忽略掉非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)
方式1:使用map和filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | object exercise _ 002 { def main(args : Array[String]) : Unit = { val list = List( 1 , 2 , 3 , 4 , "abc" ) //思路1,使用map+fliter的思路 def f 1 (n : Any) : Boolean = { n.isInstanceOf[Int] } def f 2 (n : Int) : Int = { n + 1 } def f 3 (n : Any) : Int = { n.asInstanceOf[Int] } val list 2 = list.filter(f 1 ).map(f 3 ).map(f 2 ) println( "list2=" + list 2 ) } } |
输出
这种方式虽然能够解决问题,但是显得繁琐
方式2:使用模式匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | object exercise _ 003 { def main(args : Array[String]) : Unit = { def addOne( i : Any ) : Any = { i match { case x : Int = > x + 1 case _ = > } } val list = List( 1 , 2 , 3 , 4 , "abc" ) val list 2 = list.map(addOne) println( "list2=" + list 2 ) } } |
输出
这种方式存在一个缺点是输出的序列中存在"()",然而还去不掉。
方式3:使用偏函数
1 2 3 4 5 6 7 8 9 10 | object exercise _ 004 { def main(args : Array[String]) : Unit = { def f 2 : PartialFunction[Any, Int] = { case i : Int = > i + 1 // case语句可以自动转换为偏函数 } val list 2 = List( 1 , 2 , 3 , 4 , "ABC" ).collect(f 2 ) println(list 2 ) } } |
输出
上面的 val list2 = List(1, 2, 3, 4,"ABC").collect(f2)还可以进一步简化
1 | val list 3 = List( 1 , 2 , 3 , 4 , "ABC" ).collect{ case i : Int = > i+ 1 } |
3.偏函数是如何工作的呢?为了说明该问题,看如下实例
1 2 3 4 5 6 7 8 9 10 11 | object exercise _ 005 { def main(args : Array[String]) : Unit = { val list = List( 1 , 2 , 3 , 4 , "abc" ) val addOne 3 = new PartialFunction[Any, Int] { //使用匿名子类 def isDefinedAt(any : Any) = if (any.isInstanceOf[Int]) true else false def apply(any : Any) = any.asInstanceOf[Int] + 1 } val list 3 = list.collect(addOne 3 ) println( "list3=" + list 3 ) } } |
输出
执行过程解释:
- 使用构建特质的实现类(使用的方式是PartialFunction的匿名子类)
- PartialFunction 是个特质(看源码)
- 构建偏函数时,参数形式 [Any, Int]是泛型,第一个表示参数类型,第二个表示返回参数
- 当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行isDefinedAt()如果为true ,就会执行 apply, 构建一个新的Int,对象返回
- 执行isDefinedAt() 为false 就过滤掉这个元素,即不构建新的Int对象.
- map函数不支持偏函数,因为map底层的机制就是所有循环遍历,无法过滤处理原来集合的元素
- collect函数支持偏函数
collect函数的声明语句,可以发现它所接收的参数就是偏函数
4.偏函数简化形式
声明偏函数,需要重写特质中的方法(isDefinedAt和Apply),有的时候会略显麻烦,而Scala其实提供了简单的方法。
“case语句可以自动转换为偏函数”,在上面的方式3中,我们使用的就是该特性。
2.作为参数的函数
函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:function1,即:(参数类型) => 返回类型
看如下实例
1 2 3 | def plus(x : Int) = 3 + x val result 1 = Array( 1 , 2 , 3 , 4 ).map(plus( _ )) println(result 1 .mkString( "," )) |
对于实例的说明
- map(plus(_)) 中的plus(_) 就是将plus这个函数当做一个参数传给了map,_这里代表从集合中遍历出来的一个元素。
- plus(_) 这里也可以写成 plus ,表示对 Array(1,2,3,4) 遍历,将每次遍历的元素传给plus的 x
- 进行 3 + x 运算后,返回新的Int ,并加入到新的集合 result1中
- def map[B, That](f: A => B) 的声明中的 f: A => B 一个函数
3.匿名函数
没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数
1 2 3 | val triple = (x : Double) = > 3 * x println(triple( 3 )) |
说明
- (x: Double) => 3 * x 就是匿名函数
- (x: Double) 是形参列表, => 是规定语法表示后面是函数体, 3 * x 就是函数体,如果有多行,可以 {} 换行写.
- triple 是指向匿名函数的变量。
实例:编写一个匿名函数,可以返回2个整数的和,并输出该匿名函数的类型
1 2 3 4 5 6 7 8 9 10 11 | object exercise _ 006 { def main(args : Array[String]) : Unit = { val f 1 = (n 1 : Int, n 2 : Int ) = > { println( "匿名函数被调用" ) n 1 + n 2 } println( "f1类型=" + f 1 ) println(f 1 ( 10 , 30 )) } } |
输出结果
4.高阶函数
能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)。可使应用程序更加健壮。
实例1:定义一个高阶函数,参数能够接收函数和普通数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | object exercise _ 007 { def main(args : Array[String]) : Unit = { //test 就是一个高阶函数,它可以接收f: Double => Double def test(f : Double = > Double, n 1 : Double) = { f(n 1 ) } //sum 是接收一个Double,返回一个Double def sum(d : Double) : Double = { d + d } val res = test(sum, 6.0 ) println( "res=" + res) //12 } } |
高阶函数可以返回函数类型
1 2 3 4 5 | def minusxy(x : Int) = { (y : Int) = > x - y //匿名函数 } val result 3 = minusxy( 3 )( 5 ) //函数柯里化,先传入参数3,得到(y:Int)=3-y,再传入5,得到3-5=-2,最后返回计算结果 println(result 3 ) |
说明: def minusxy(x: Int) = (y: Int) => x - y
- 函数名为 minusxy
- 该函数返回一个匿名函数 (y: Int) = > x -y
说明val result3 = minusxy(3)(5)
- minusxy(3)执行minusxy(x: Int)得到 (y: Int) => 3 - y 这个匿名函
- minusxy(3)(5)执行 (y: Int) => x - y 这个匿名函数
- 也可以分步执行: val f1 = minusxy(3); val res3 = f1(5)
5.类型推断
1.基本介绍
参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如list=(1,2,3) list.map() map中函数参数类型是可以推断的),同时也可以进行相应的简写。
2.参数类型推断写法说明
- 参数类型是可以推断时,可以省略参数类型
- 当传入的函数,只有单个参数时,可以省去括号
- 如果变量只在=>右边只出现一次,可以用_来代替
3.实例:将集合中每个元素的值加上1,并返回值。观察演化过程
1 2 3 4 5 6 7 8 9 10 | object exercise _ 009 { def main(args : Array[String]) : Unit = { val list = List( 1 , 2 , 3 , 4 ) println(list.map((x : Int) = >x + 1 )) //(2,3,4,5) println(list.map((x) = >x + 1 )) println(list.map(x = >x + 1 )) println(list.map( _ + 1 )) val res = list.reduce( _ + _ ) } } |
说明
- map是一个高阶函数,因此也可以直接传入一个匿名函数,完成map
- 当遍历list时,参数类型是可以推断出来的,可以省略数据类型Int,println(list.map((x)=>x + 1))
- 当传入的函数,只有单个参数时,可以省去括号,println(list.map(x=>x + 1))
- 如果变量只在=>右边只出现一次,可以用_来代替,println(list.map(_ + 1))
6.函数柯里化
1. 基本介绍
- 函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化
- 柯里化就是证明了函数只需要一个参数而已。
- 不用设立柯里化存在的意义这样的命题。柯里化就是以函数为主体这种思想发展的必然产生的结果。(即:柯里化是面向函数思想的必然产生结果)
2.函数柯里化快速入门
编写一个函数,接收两个整数,可以返回两个数的乘积,要求:
- 使用常规的方式完成
- 使用闭包的方式完成
- 使用函数柯里化完成
1 2 3 4 5 6 7 8 9 10 11 | object exercise _ 010 { def main(args : Array[String]) : Unit = { //说明 def mul(x : Int, y : Int) = x * y println(mul( 10 , 10 )) def mulCurry(x : Int) = (y : Int) = > x * y println(mulCurry( 10 )( 9 )) def mulCurry 2 (x : Int)(y : Int) = x * y println(mulCurry 2 ( 10 )( 8 )) } } |
输出结果
3.使用函数柯里化
比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
- 全部转大写(或小写)
- 比较是否相等
针对这两个操作,我们用一个函数处理的思想,其实也变成了两个函数处理的思想(柯里化)
方式1: 简单的方式,使用一个函数完成.
1 2 3 | def eq 2 (s 1 : String)(s 2 : String) : Boolean = { s 1 .toLowerCase == s 2 .toLowerCase } |
方式2:使用稍微高级的用法(隐式类):形式为 str.方法()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | object exercise _ 011 { def main(args : Array[String]) : Unit = { def eq(s 1 : String, s 2 : String) : Boolean = { s 1 .equals(s 2 ) } implicit class TestEq(s : String) { // 体现了将比较字符串的问题,分解成了两个任务完成 // 1.checkEq完成转换大小写 // 2.f函数完成比较任务 def checkEq(ss : String)(f : (String, String) = > Boolean) : Boolean = { f(s.toLowerCase, ss.toLowerCase) } } val str 1 = "HellO" print(str 1 .checkEq( "HELLO" )( _ .equals( _ ))) } } |
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程