Scala核心编程(十三)函数式编程高级
一、偏函数(partial function)1.1 先看一个需求给你一个集合val list List(1, 2, 3, 4, abc)请完成如下要求将集合list中的所有数字1并返回一个新的集合要求忽略掉非数字的元素即返回的新的集合形式为(2, 3, 4, 5)1.2 解决方式-map返回新的集合引出偏函数思路1-mapfilter方式vallistList(1,2,3,4,abc)//思路1,使用mapfilter的思路deff1(n:Any):Boolean{n.isInstanceOf[Int]}deff2(n:Int):Int{n1}deff3(n:Any):Int{n.asInstanceOf[Int]}vallist2list.filter(f1).map(f3).map(f2)println(list2list2)思路2-模式匹配defaddOne(i:Int):Int{imatch{casex:Intx1case__}}// 改进defaddOne2(i:Any):Any{imatch{casex:Intx1case_}}vallistList(1,2,3,4,abc)vallist2list.map(addOne2)println(list2list2)1.3 基本介绍在对符合某个条件而不是所有情况进行逻辑操作时使用偏函数是一个不错的选择将包在大括号内的一组case语句封装为函数我们称之为偏函数它只对会作用于指定类型的参数或指定范围值的参数实施计算超出范围的值会忽略未必会忽略这取决于你打算怎样处理偏函数在Scala中是一个特质PartialFunction1.4 偏函数快速入门使用偏函数解决前面的问题vallistList(1,2,3,4,abc)//说明valaddOne3newPartialFunction[Any,Int]{defisDefinedAt(any:Any)if(any.isInstanceOf[Int])trueelsefalsedefapply(any:Any)any.asInstanceOf[Int]1}vallist3list.collect(addOne3)println(list3list3)//?1.5 偏函数的小结valaddOne3newPartialFunction[Any,Int]{defapply(any:Any)any.asInstanceOf[Int]1defisDefinedAt(any:Any)if(any.isInstanceOf[Int])trueelsefalse}使用构建特质的实现类使用的方式是PartialFunction的匿名子类PartialFunction是个特质看源码构建偏函数时参数形式[Any, Int]是泛型第一个表示参数类型第二个表示返回参数当使用偏函数时会遍历集合的所有元素编译器执行流程时先执行isDefinedAt()如果为true就会执行 apply构建一个新的Int对象返回执行isDefinedAt()为false 就过滤掉这个元素即不构建新的Int对象。map函数不支持偏函数因为map底层的机制就是所有循环遍历无法过滤处理原来集合的元素collect函数支持偏函数1.6 偏函数简化形式声明偏函数需要重写特质中的方法有的时候会略显麻烦而Scala其实提供了简单的方法简化形式1deff2:PartialFunction[Any,Int]{casei:Inti1// case语句可以自动转换为偏函数}vallist2List(1,2,3,4,ABC).collect(f2)简化形式2vallist3List(1,2,3,4,ABC).collect{casei:Inti1}println(list3)二、作为参数的函数2.1 基本介绍函数作为一个变量传入到了另一个函数中那么该作为参数的函数的类型是function1即(参数类型) 返回类型2.2 应用实例//说明defplus(x:Int)3x//说明valresult1Array(1,2,3,4).map(plus(_))println(result1.mkString(,))2.3 应用实例小结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 基本介绍没有名字的函数就是匿名函数可以通过函数表达式来设置匿名函数3.2 应用实例valtriple(x:Double)3*x println(triple(3))说明(x: Double) 3 * x就是匿名函数(x: Double)是形参列表是规定语法表示后面是函数体3 * x就是函数体如果有多行可以{}换行写。triple是指向匿名函数的变量。3.3 课堂案例请编写一个匿名函数可以返回2个整数的和并输出该匿名函数的类型。valf1(n1:Int,n2:Int){println(匿名函数被调用)n1n2}println(f1类型f1)println(f1(10,30))四、高阶函数4.1 基本介绍能够接受函数作为参数的函数叫做高阶函数higher-order function。可使应用程序更加健壮。4.2 高阶函数基本使用//test 就是一个高阶函数它可以接收f: Double Doubledeftest(f:DoubleDouble,n1:Double){f(n1)}//sum 是接收一个Double,返回一个Doubledefsum(d:Double):Double{dd}valrestest(sum,6.0)println(resres)4.3 高阶函数可以返回函数类型defminusxy(x:Int){(y:Int)x-y//匿名函数}valresult3minusxy(3)(5)println(result3)4.4 高级函数案例的小结说明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 res f1(90)4.5 课堂练习objectTemp{defmain(args:Array[String]):Unit{deftest1(x:Double){(y:Double)x*x*y//}valrestest1(2.0)(3.0)println(resres)// 输出什么}}res 12.0五、参数(类型)推断5.1 基本介绍参数推断省去类型信息在某些情况下[需要有应用场景]参数类型是可以推断出来的如list(1,2,3) list.map()map中函数参数类型是可以推断的同时也可以进行相应的简写。5.2 参数类型推断写法说明参数类型是可以推断时可以省略参数类型当传入的函数只有单个参数时可以省去括号如果变量只在右边只出现一次可以用_来代替5.3 应用案例//分别说明vallistList(1,2,3,4)println(list.map((x:Int)x1))//(2,3,4,5)println(list.map((x)x1))println(list.map(xx1))println(list.map(_1))valreslist.reduce(__)5.4 应用案例的小结map是一个高阶函数因此也可以直接传入一个匿名函数完成map当遍历list时参数类型是可以推断出来的可以省略数据类型Intprintln(list.map((x)x1))当传入的函数只有单个参数时可以省去括号println(list.map(xx1))如果变量只在右边只出现一次可以用_来代替println(list.map(_1))六、闭包(closure)6.1 基本介绍闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)。6.2 案例演示//1.用等价理解方式改写 2.对象属性理解defminusxy(x:Int)(y:Int)x-y//f函数就是闭包.valfminusxy(20)println(f(1)f(1))// 19println(f(2)f(2))// 186.3 代码小结(y: Int) x - y返回的是一个匿名函数因为该函数引用到函数外的 x那么该函数和x整体形成一个闭包。如这里val f minusxy(20)的f函数就是闭包你可以这样理解返回函数是一个对象而x就是该对象的一个字段他们共同形成一个闭包当多次调用f时可以理解多次调用闭包发现使用的是同一个x所以x不变。在使用闭包时主要搞清楚返回函数引用了函数外的哪些变量因为他们会组合成一个整体(实体)形成一个闭包6.4 闭包的最佳实践请编写一个程序具体要求如下编写一个函数makeSuffix(suffix: String)可以接收一个文件后缀名(比如.jpg)并返回一个闭包调用闭包可以传入一个文件名如果该文件名没有指定的后缀(比如.jpg)则返回 文件名.jpg如果已经有.jpg后缀则返回原文件名。要求使用闭包的方式完成String.endsWith(xx)6.5 体会闭包的好处返回的匿名函数和makeSuffix(suffix string)的suffix变量组合成一个闭包因为返回的函数引用到suffix这个变量我们体会一下闭包的好处如果使用传统的方法也可以轻松实现这个功能但是传统方法需要每次都传入后缀名比如 .jpg而闭包因为可以保留上次引用的某个值所以我们传入一次就可以反复使用。七、函数柯里化(curry)7.1 基本介绍函数编程中接受多个参数的函数都可以转化为接受单个参数的函数这个转化过程就叫柯里化柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中已经涉及到了柯里化操作。不用设立柯里化存在的意义这样的命题。柯里化就是以函数为主体这种思想发展的必然产生的结果。即柯里化是面向函数思想的必然产生结果7.2 函数柯里化快速入门编写一个函数接收两个整数可以返回两个数的乘积要求使用常规的方式完成使用闭包的方式完成使用函数柯里化完成//说明defmul(x:Int,y:Int)x*y println(mul(10,10))defmulCurry(x:Int)(y:Int)x*y println(mulCurry(10)(9))defmulCurry2(x:Int)(y:Int)x*y println(mulCurry2(10)(8))7.3 函数柯里化最佳实践比较两个字符串在忽略大小写的情况下是否相等注意这里是两个任务全部转大写或小写比较是否相等针对这两个操作我们用一个函数去处理的思想其实也变成了两个函数处理的思想柯里化方式1简单的方式使用一个函数完成defeq2(s1:String)(s2:String):Boolean{s1.toLowerCases2.toLowerCase}方式2使用稍微高级的用法(隐式类)形式为 str.方法()defeq(s1:String,s2:String):Boolean{s1.equals(s2)}implicitclassTestEq(s:String){defcheckEq(ss:String)(f:(String,String)Boolean):Boolean{f(s.toLowerCase,ss.toLowerCase)}}//案例演示说明简化版(三种形式直接传入匿名函数方式)str1.checkEq(str2)(_.equals(_))八、控制抽象8.1 看一个需求如何实现将一段代码(从形式上看)作为参数传递给高阶函数在高阶函数内部执行这段代码。其使用的形式如breakable{}。varn10breakable{while(n20){n1if(n18){break()}}}8.2 控制抽象基本介绍控制抽象是这样的函数满足如下条件参数是函数函数参数没有输入值也没有返回值8.3 快速入门defmyRunInThread(f1:()Unit){newThread{overridedefrun():Unit{f1()}}.start()}myRunInThread{()println(干活咯5秒完成...)Thread.sleep(5000)println(干完咯)}8.4 简化处理省略()如下形式defmyRunInThread(f1:Unit):Unit{//说明newThread{overridedefrun():Unit{f1}}.start()}myRunInThread{//说明println(干活咯5秒完成...)Thread.sleep(5000)println(干完咯)}8.5 进阶用法实现类似while的until函数varx10defuntil(condition:Boolean)(block:Unit):Unit{//类似while循环递归if(!condition){block until(condition)(block)}}until(x0){x-1println(xx)}