0
点赞
收藏
分享

微信扫一扫

ScalaNote22-函数式高级编程


偏函数

先看一个需求,给你一个集合val list = List(1, 2, 3, 4, “abc”) ,请完成如下要求:
将集合list中的所有数字+1,并返回一个新的集合
要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)

用之前学过的知识,这个题有两个解法,第一个是利用filter和map函数

  • filter筛选出Int的元素,注意此时返回的Any类型
  • 把filter之后的元素再转换成Int类型
  • map对筛选出来的元素计算+1

val list = List(1, 2, 3, 4, "abc") 
def isInt(x:Any)={
// 返回true false
x.isInstanceOf[Int]
}
def asInt(x:Any)={
// 返回true false
x.asInstanceOf[Int]
}
def andOne(x:Int)={
x+1
}
// 当然asInt和andOne可以写在一起,这样省点事
list.filter(isInt).map(asInt).map(andOne)

list: List[Any] = List(1, 2, 3, 4, abc)
isInt: (x: Any)Boolean
asInt: (x: Any)Int
andOne: (x: Int)Int
res3: List[Int] = List(2, 3, 4, 5)

也可以用模式匹配写

// 此时如果x非Int,返回(),所以还得再filter
def matchF(x:Any)={
x match{
case x:Int => x+1
case _ =>
}
}
println(list.map(matchF))
println(list.map(matchF).filter(isInt))

List(2, 3, 4, 5, ())
List(2, 3, 4, 5)





matchF: (x: Any)AnyVal

上述两种方法都要进行filter和map两步操作,偏函数可以一步到位~

  • 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择
  • 将包在大括号内的一组case语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出范围的值会忽略(未必会忽略,这取决于你打算怎样处理)
  • 偏函数在Scala中是一个特质

直接上代码吧~

val list = List(1, 2, 3, 4, "abc")
//定义一个偏函数
//1. PartialFunction[Any,Int] 表示偏函数接收的参数类型是Any,返回类型是Int
//2. isDefinedAt(x: Any) 如果返回true ,就会去调用 apply 构建对象实例,如果是false,过滤
//3. apply 构造器 ,对传入的值 + 1,并返回(新的集合)
//4. isDefinedAt和apply方法是固定的,不可以随便定义方法名称
val addOne3= new PartialFunction[Any, Int] {
def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
def apply(any: Any) = any.asInstanceOf[Int] + 1
}
val list3 = list.collect(addOne3)
println("list3=" + list3)

list3=List(2, 3, 4, 5)





list: List[Any] = List(1, 2, 3, 4, abc)
addOne3: PartialFunction[Any,Int] = <function1>
list3: List[Int] = List(2, 3, 4, 5)

  • 使用构建特质的实现类(使用的方式是PartialFunction的匿名子类)
  • PartialFunction 是个特质(看源码)
  • 构建偏函数时,参数形式[Any, Int]是泛型,第一个表示参数类型,第二个表示返回参数
  • 当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行isDefinedAt()如果为true ,就会执行 apply, 构建一个新的Int对象返回
  • 执行isDefinedAt() 为false 就过滤掉这个元素,即不构建新的Int对象
  • map函数不支持偏函数,因为map底层的机制就是所有循环遍历,无法过滤处理原来集合的元素
  • collect函数支持偏函数

上面偏函数也有其他简化形式,后面遇到我应该也不会写了吧。。。直接把代码Copy过来~

def f2: PartialFunction[Any, Int] = {
case i: Int => i + 1 // case语句可以自动转换为偏函数
}
List(1, 2, 3, 4,"ABC").collect(f2)

f2: PartialFunction[Any,Int]
res10: List[Int] = List(2, 3, 4, 5)

List(1, 2, 3, 4,"ABC").collect{ case i: Int => i + 1 
case i:String=>i*2}

res13: List[Any] = List(2, 3, 4, 5, ABCABC)

作为参数的函数

这个其实在map中用过,简单看个例子:

//说明
def plus(x: Int) = 3 + x
//说明
println(Array(1, 2, 3, 4).map(plus(_)).mkString(","))
println(Array(1, 2, 3, 4).map(plus).mkString(","))

4,5,6,7
4,5,6,7





plus: (x: Int)Int

  • ​map(plus(_)) 中的plus(_) 就是将plus这个函数当做一个参数传给了map,_这里代表从集合中遍历出来的一个元素​
  • ​plus(_) 这里也可以写成 plus 表示对 Array(1,2,3,4) 遍历,将每次遍历的元素传给plus的 x!​

匿名函数

没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数!

val triple = (x: Double) => 3 * x
println(triple(3))
println(triple.getClass)

9.0
class $line39.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anonfun$1





triple: Double => Double = <function1>

  • (x: Double) => 3 * x 就是匿名函数
  • (x: Double) 是形参列表, => 是规定语法表示后面是函数体, 3 * x 就是函数体,如果有多行,可以 {} 换行写
  • triple 是指向匿名函数的变量

再看个例子:

val f1 = (n1: Int, n2: Int ) => {
println("biubiubiu")
n1 + n2
}
println(f1.getClass)
println("f1:" + f1)
println(f1(10, 30))

class $line41.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anonfun$1
f1:<function2>
biubiubiu
40





f1: (Int, Int) => Int = <function2>

高阶函数

能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)。可使应用程序更加健壮

//test 就是一个高阶函数,它可以接收f: Double => Double ,f的参数和返回值都为Double
def test(f: Double => Double, n1: Double) = {
f(n1)
}
//sum 是接收一个Double,返回一个Double
def sum(d: Double): Double = {
d + d
}
val res = test(sum, 6.0)
println("res=" + res)

res=12.0





test: (f: Double => Double, n1: Double)Double
sum: (d: Double)Double
res: Double = 12.0

上面的例子是把函数作为参数,我们也可以定义一个函数,使之结果返回为一个函数

// 相当于把x固定了返回的新函数中,y是参数
def minusxy(x: Int) = {
(y: Int) => x-y //匿名函数
}
//这一步相当于先返回个函数(3-y),然后y=5
val result3 = minusxy(3)(5)
// println(result3)

minusxy: (x: Int)Int => Int
result3: Int = -2

参数类型推断

据说这里的应用很多,我暂时没遇到过。参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如list=(1,2,3) list.map(),map中函数参数类型是可以推断的,因为list中元素都是Int),同时也可以进行相应的简写。
参数类型推断写法说明:

  • 参数类型是可以推断时,可以省略参数类型
  • 当传入的函数,只有单个参数时,可以省去括号
  • 如果变量只在=>右边只出现一次,可以用_来代替

val list = List(1, 2, 3, 4)
//匿名函数写法
println(list.map((x:Int)=>x + 1)) //(2,3,4,5)
//list中都是Int,可以简写
println(list.map((x)=>x + 1)) //(2,3,4,5)
println(list.map(x=>x + 1)) //(2,3,4,5)
//x在右边只出现一次,直接_简写
println(list.map( _ + 1)) //(2,3,4,5)
// reduce好像默认是reduceLeft?从左边开始计算,把元素一次传进去
println(list.reduce((n1:Int ,n2:Int) => n1 + n2)) //10
println(list.reduce((n1 ,n2) => n1 + n2)) //10
println(list.reduce( _ + _)) //10

List(2, 3, 4, 5)
List(2, 3, 4, 5)
List(2, 3, 4, 5)
List(2, 3, 4, 5)
10
10
10





list: List[Int] = List(1, 2, 3, 4)

闭包

前面高阶函数中,返回值为函数的case已经涉及到了闭包。闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)。看个例子:

//1.用等价理解方式改写 2.对象属性理解
def minusxy(x: Int) = (y: Int) => x - y
//f函数就是闭包.这玩意感觉就是方便后面使用时,不需要重复把x=20传进去
val f = minusxy(20)
println("f(1)=" + f(1)) // 19
println("f(2)=" + f(2)) // 18

f(1)=19
f(2)=18





minusxy: (x: Int)Int => Int
f: Int => Int = <function1>

再来看个实际case:

  • 编写一个函数 makeSuffix(suffix: String) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包
  • 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回 文件名.jpg , 如果已经有.jpg后缀,则返回原文件名
  • 要求使用闭包的方式完成
  • 后缀名可以使用String.endsWith(xx),这个函数类似截取最后length(xx)个字符串和xx比较,返回true false

def makeSuffix(suffix: String) ={
(fileName:String)=> if( fileName.endsWith(suffix)) fileName else fileName+suffix
}
val f = makeSuffix(".jpg")
println(f("dog.jpg")) // dog.jpg
println(f("cat")) // cat.jpg

dog.jpg
cat.jpg





makeSuffix: (suffix: String)String => String
f: String => String = <function1>

函数柯里化

韩老师讲的一个例子很懵逼,有点绕,但是如果只看函数柯里化的形式,似乎又不是很复杂。

  • 函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化
  • 柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里化操作

上面闭包和高阶函数的case就用到所谓的函数柯里化

def mulCurry1(x: Int,y:Int) = x * y
def mulCurry2(x: Int)(y:Int) = x * y
println(mulCurry1(10,8))
println(mulCurry2(10)(8))

80
80





mulCurry1: (x: Int, y: Int)Int
mulCurry2: (x: Int)(y: Int)Int

控制抽象

控制抽象学着更是蒙蔽。。。先看定义:
控制抽象是函数且满足如下条件

  • 参数是函数
  • 函数参数没有输入值也没有返回值

比如之前学习的breakable{}函数,里面放的是while的循环体,可以把代码块当成一个函数。直接看韩老师给的case吧

var x = 10
def until(condition: => Boolean)(block: => Unit): Unit = {
//类似while循环,递归
if (!condition) {
block
until(condition)(block)
}
// println("x=" + x)
// println(condition)
// block
// println("x=" + x)
}
// x==0是condition,后面的代码块是block参数
//如果condition不满足,则执行block,并且递归调用until
until(x == 0){
x -= 1
println("x=" + x)
}

x=9
x=8
x=7
x=6
x=5
x=4
x=3
x=2
x=1
x=0





x: Int = 0
until: (condition: => Boolean)(block: => Unit)Unit

                                2020-03-17 于南京市栖霞区


举报

相关推荐

0 条评论