Scala - 08 - 函数式编程:高阶函数
发布时间:2020-12-16 09:02:47 所属栏目:安全 来源:网络整理
导读:函数式编程的崛起 函数式编程中的 “值不可变性” 避免了对公共的可变状态进行同步访问控制的复杂问题,能够较好满足分布式并行编程的需求,适应大数据时代的到来。 函数是第一等公民 可以作为实参传递给另外一个函数 可以作为返回值 可以赋值给变量 可以存
函数式编程的崛起
函数式编程中的
“值不可变性”避免了对公共的可变状态进行同步访问控制的复杂问题,能够较好满足分布式并行编程的需求,适应大数据时代的到来。
函数是第一等公民
def greeting() = (name: String) => { s"Hello" + " " + name } //> greeting: ()String => String greeting()("World") //> res0: String = Hello World def greeting2(age: Int) = (name: String) => { s"Hello $name,your age is $age" } //> greeting2: (age: Int)String => String greeting2(29)("Anliven") //> res2: String = Hello Anliven,your age is 29 函数类型和值
在函数式编程中,函数的使用方式和其他数据类型的使用方式完全一致,可以像任何其他数据类型一样被传递和操作。
也就是说,可以像定义变量那样去定义一个函数,函数也会有“值”,函数的“值”就是“
函数字面量(Funciton Literal)”,也称为
函数文字量或
函数常量。实际上,这个函数字面量其实就是一个
匿名函数。
需要注意:
def test(x: Int): Int = { x + 1 } //> test: (x: Int)Int def test1(x: Int) = x + 1 //> test1: (x: Int)Int val test2: Int => Int = { (x: Int) => x + 1 } //> test2 : Int => Int = testrepl$$$Lambda$8/[email?protected] val test3 = { (x: Int) => x + 1 } //> test3 : Int => Int = testrepl$$$Lambda$9/[email?protected] def test5(x: Int,y: Int) = x + y //> test5: (x: Int,y: Int)Int var test6 = (x: Int,y: Int) => x + y //> test6 : (Int,Int) => Int = testrepl$$$Lambda$10/[email?protected] test6(2,6)
示例说明:
第7行把函数"(x: Int,y: Int) => x + y"作为一个值(函数字面量)赋给test6变量。
由此可见,Scala中的函数和普通变量的使用方式完全一致。
匿名函数
使用匿名函数(Anonymous Function),可以不需要给每个函数命名,大大简化代码编写工作。
匿名函数的定义形式(也称为“
Lamda表达式”):
(参数) => {表达式}
注意:如果参数只有一个,可省略参数的圆括号;如果表达式只有一行,可省略表达式的花括号。
val myNum: Int => Int = (x: Int) => { x * 2 } //> myNum : Int => Int = testrepl$$$Lambda$3/[email?protected] val myNum2 = (x: Int) => x * 2 //> myNum2 : Int => Int = testrepl$$$Lambda$9/[email?protected] val myNum3: Int => Int = (x) => x * 2 //> myNum3 : Int => Int = testrepl$$$Lambda$10/[email?protected] myNum(3) //> res0: Int = 6 myNum2(3) //> res1: Int = 6 myNum3(3) //> res2: Int = 6 def test1(x: Int): Int = { x * x } //> test1: (x: Int)Int def test2(x: Int) = x * x //> test2: (x: Int)Int (x: Int) => x * x //> res0: Int => Int = testrepl$$$Lambda$8/[email?protected] val test3 = (x: Int) => x * x //> test3 : Int => Int = testrepl$$$Lambda$9/[email?protected] val test4: Int => Int = (x) => x * x //> test4 : Int => Int = testrepl$$$Lambda$10/[email?protected] test1(3) //> res1: Int = 9 test2(3) //> res2: Int = 9 test3(3) //> res3: Int = 9 test4(3) //> res4: Int = 9
示例说明:
注意:类型声明“Int=>Int”和x的类型声明,不可以同时省略,因为全部省略以后,解释器也无法推断出类型。
def test1(x: Int,y: Int): Int = { x + y } //> test1: (x: Int,y: Int)Int def test2(x: Int,y: Int) = x + y //> test2: (x: Int,y: Int)Int (x: Int,y: Int) => x + y //> res0: (Int,Int) => Int = testrepl$$$Lambda$8/[email?protected] var test3 = (x: Int,y: Int) => x + y //> test3 : (Int,Int) => Int = testrepl$$$Lambda$9/[email?protected] var test4: (Int,Int) => Int = (x,y) => { x + y } //> test4 : (Int,Int) => Int = testrepl$$$Lambda$10/[email?protected] test1(2,6) //> res1: Int = 8 test2(2,6) //> res2: Int = 8 test3(2,6) //> res3: Int = 8 test4(2,6) //> res4: Int = 8 闭包
闭包是一个比较特殊的函数,反映了一个从开放到封闭的过程,返回值依赖于声明在函数外部的一个或多个变量。
函数引用的外部变量,必须在函数外部给出值。
闭包示例-1:
var more = 1 //> more : Int = 1 val addMore = (x: Int) => x + more //> addMore : Int => Int = testrepl$$$Lambda$9/[email?protected] addMore(10) //> res0: Int = 11 more = 9 addMore(10) //> res1: Int = 19
示例说明:
函数定义“
val addMore = (x: Int) => x + more”中,引用了没有在函数中定义的外部变量more,而more是一个自由变量,还没有绑定具体的值,此时这个函数是“
开放的”;而变量x是一个已在函数中明确定义的变量,只有在调用的时候才被赋值。
外部变量more确定具体值(“
?var more = 1”)以后,那么函数addMore中的more变量也就被绑定具体值了,不再是“自由变量”,此时这个函数是“关闭的”。
另外,每次addMore函数被调用时都会
创建一个新闭包。每个闭包都会访问闭包创建时活跃的more变量。
闭包示例-2:
def plusStep(step: Int) = (num: Int) => num + step //> plusStep: (step: Int)Int => Int val myFunc = plusStep(3) //> myFunc : Int => Int = testrepl$$$Lambda$8/[email?protected] println(myFunc(10)) //> 13
示例说明:
step是一个自由变量,它的值只有在运行的时候才能确定,num的类型是确定的,num的值只有在调用的时候才被赋值。
这样的函数,被称为“闭包”,它反映了一个从开放到封闭的过程。
高阶函数
用函数作为形参或返回值的函数,称为高阶函数。
也就是说,高阶函数就是一个接受其他函数作为参数或者返回一个函数的函数。
高阶函数示例-1:
def f(x: Int,y: Int) = x + y //> f: (x: Int,y: Int)Int def operate(f: (Int,Int) => Int) = { f(4,4) } //> operate: (f: (Int,Int) => Int)Int operate(f)
示例说明:函数operate是一个接受函数参数的函数,因此是一个高阶函数。
高阶函数示例-2:
//给定两个数区间中的所有整数求和 def sumInts(a: Int,b: Int): Int = { if (a > b) 0 else a + sumInts(a + 1,b) } //> sumInts: (a: Int,b: Int)Int sumInts(1,5) //> res0: Int = 15 //定义了一个新的函数sum,以函数f为参数 def sum(f: Int => Int,a: Int,b: Int): Int = { if (a > b) 0 else f(a) + sum(f,a + 1,b) } //> sum: (f: Int => Int,b: Int)Int //定义了一个新的函数self,该函数的输入是一个整数x,然后直接输出x自身 def self(x: Int): Int = x //> self: (x: Int)Int //重新定义sumInts函数 def sumInts2(a: Int,b: Int): Int = sum(self,a,b) //> sumInts2: (a: Int,b: Int)Int sumInts2(1,5) //> res1: Int = 15
示例说明:函数sum的参数类型是(Int=>Int,Int,Int),结果类型是Int,也就是说函数sum是一个接受函数参数的高阶函数。
高阶函数示例-3:
def sum(f: Int => Int,b: Int)Int def self(x: Int): Int = x //> self: (x: Int)Int def square(x: Int): Int = x * x //> square: (x: Int)Int def powerOfTwo(x: Int): Int = if (x == 0) 1 else 2 * powerOfTwo(x - 1) //> powerOfTwo: (x: Int)Int def sumInts(a: Int,b) //> sumInts: (a: Int,b: Int)Int def sumSquared(a: Int,b: Int): Int = sum(square,b) //> sumSquared: (a: Int,b: Int)Int def sumPowersOfTwo(a: Int,b: Int): Int = sum(powerOfTwo,b) //> sumPowersOfTwo: (a: Int,b: Int)Int println(sumInts(1,5)) //> 15 println(sumSquared(1,5)) //> 55 println(sumPowersOfTwo(1,5)) //> 62
示例说明:
占位符语法
使用下划线作为一个或多个参数的占位符,只要每个参数在函数字面量内仅出现一次。
println("Testing,Scala!") //> Testing,Scala! val numList = List(-3,-5,1,6,9) //> numList : List[Int] = List(-3,9) numList.filter(x => x > 0) //> res0: List[Int] = List(1,9) numList.filter(_ > 0) //> res1: List[Int] = List(1,9)
示例说明:
当采用下划线的表示方法时,对于列表numList中的每个元素,都会依次传入用来替换下划线。
比如,首先传入-3,判断-3>0是否成立,是则把该值放入结果集合,否则舍弃;然后传入-5,判断-5>0是否成立,依此类推。
柯里化
柯里化函数(Curried Funciton)把具有多个参数的函数转化为一条函数链,每个节点上是单一参数。
在函数式编程中,可以基于一些通用性的函数,利用柯里化函数等来构造新函数,而不需要重新定义新函数。
示例:
def add(x: Int,y: Int) = x + y //> add: (x: Int,y: Int)Int add(1,2) //> res0: Int = 3 def addCurried(x: Int)(y: Int) = x + y //> addCurried: (x: Int)(y: Int)Int addCurried(1)(2) //> res1: Int = 3 val addOne = addCurried(1)_ //> addOne : Int => Int = TestScala$$$Lambda$8/[email?protected] addOne(2) //> res2: Int = 3
示例说明:
函数add和addCurried的函数定义时等价的。
“addCurried(1)_”的下划线是通配后面所有的参数列表。
递归
在函数式编程中利用递归函数(Recursive Funtion)实现循环。
def factorial(n: Int): Int = if (n <= 0) 1 else n * factorial(n - 1) //> factorial: (n: Int)Int factorial(5) //> res0: Int = 120 尾递归
在尾递归函数(Tail?Recursive Funtion)中所有递归形式的调用都出现在函数的末尾。
当编译器检测到一个函数调用时尾递归的时候,它就覆盖当前的活动记录,而不是在栈中去创建一个新的。
Scala编译器不会主动进行尾递归优化,需要“@annotation.tailrec”来 告知Scala编译器
package testscala object TestScala { def main(args: Array[String]) { println("Testing,Scala!") val res = factorial2(5,1) println(res) } @annotation.tailrec def factorial2(n: Int,m: Int): Int = if (n <= 0) m else factorial2(n - 1,m * n) } 示例:求整数a到b的相加之和def sum(f: Int => Int)(a: Int)(b: Int): Int = { @annotation.tailrec def loop(n: Int)(acc: Int): Int = { if (n > b) { println(s"n=${n},acc=${acc}") acc } else { println(s"n=${n},acc=${acc}") loop(n + 1)(acc + f(n)) } } loop(a)(0) } //> sum: (f: Int => Int)(a: Int)(b: Int)Int sum(x => x)(1)(5) //> n=1,acc=0 //| n=2,acc=1 //| n=3,acc=3 //| n=4,acc=6 //| n=5,acc=10 //| n=6,acc=15 //| res0: Int = 15 sum(x => x * x)(1)(5) //> n=1,acc=5 //| n=4,acc=14 //| n=5,acc=30 //| n=6,acc=55 //| res1: Int = 55 sum(x => x * x * x)(1)(5) //> n=1,acc=9 //| n=4,acc=36 //| n=5,acc=100 //| n=6,acc=225 //| res2: Int = 225 val square = sum(x => x * x)_ //> square : Int => (Int => Int) = TestScala$$$Lambda$13/[email?protected] square(1)(5) //> n=1,acc=55 //| res3: Int = 55 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |