函数式编程基础
-
函数
-
递归
-
过程
-
惰性函数
-
异常
函数式编程高级(后续学习)
-
偏函数
-
值函数
-
作为参数的函数
-
匿名函数
-
参数(类型)推断
-
高阶函数
-
闭包
-
柯里化
-
抽象控制
1.函数
1.1 函数式编程介绍
-
在scala中,方法和函数几乎可以等同(比如他们的定义、使用、运行机制都一样的),只是函数的使用方式更加的灵活多样。
-
函数式编程是从编程方式(范式)的角度来谈的,可以这样理解:函数式编程把函数当做一等公民,充分利用函数、 支持的函数的多种使用方式。比如:在Scala当中,函数是一等公民,像变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量,函数的创建不用依赖于类或者对象,而在Java当中,函数的创建则要依赖于类、抽象类或者接口。
-
面向对象编程是以对象为基础的编程方式。
-
在scala中函数式编程和面向对象编程融合在一起了 。
1.2 函数定义/声明
为完成某一功能的程序指令(语句)的集合,称为函数。
// 基本语法
def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] {
语句...
return 返回值
}
// 1.函数声明关键字为def (definition)
// 2.[参数名: 参数类型], ...:表示函数的输入(就是参数列表), 可以没有。 如果有,多个参数使用逗号间隔
// 3.函数中的语句:表示为了实现某一功能代码块
// 4.函数可以有返回值,也可以没有
// 5.返回值形式1: : 返回值类型 =
// 6.返回值形式2: = 表示返回值类型不确定,使用类型推导完成
// 7.返回值形式3: 表示没有返回值,return 不生效
// 8.如果没有return ,默认以执行到最后一行的结果作为返回值
// 示例:
// (1)函数示例1:返回Unit类型的函数。
def f1() : Unit = {
println(“f1”)
}
//(2)函数示例2:返回Unit类型的函数,但是没有显式指定返回类型。
def f2() = {
println(“f2”)
}
//(3)函数示例3:返回值类型有多种可能,此时也可以省略Unit。
def f3(content: String) = {
if(content.length >= 3)
content + "喵喵喵~"
else
3
}
// (4)函数示例4:带有默认值参数的函数,调用该函数时,可以只给无默认值的参数传递值,也可以都传递,新值会覆盖默认值;传递参数时如果不按照定义顺序,则可以通过参数名来指定。
def f4(content: String, leg: Int = 4) = {
println(content + "," + leg)
}
//(5)函数示例5:变长参数(不确定个数参数,类似Java的...)
def sum(args: Int*) = {
var result = 0
for(arg <- args)
result += arg
result
}
//(6)函数示例6:递归函数在使用时必须有明确的返回值类型
object Test {
def main(args: Array[String]): Unit = {
test(5)
test2(3)
}
def test (n: Int) {
if (n > 2) {
test (n - 1)
}
println("n=" + n)
}
def test2 (n: Int) {
if (n > 2) {
test2 (n - 1)
}else {
println("n=" + n)
}
}
}
说明:函数注意事项和细节讨论:
-
函数的形参列表可以是多个, 如果函数没有形参,调用时可以不带()。
-
形参列表和返回值列表的数据类型可以是值类型和引用类型。
-
Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下,retur关键字可以省略。
-
因为Scala可以自行推断,所以在省略retur关键字的场合,返回值类型也可以省略。
-
如果函数明确使用retur关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然如果你什么都不写,即使有return 返回值为()。
-
如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值。
-
如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为Any)。
-
Scala语法中任何的语法结构都可以嵌套其他语法结构(灵活),即:函数中可以再声明/定义函数,类中可以再声明类 ,方法中可以再声明/定义方法。
-
Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值。
-
Scala函数支持可变参数。scala 函数的形参默认是val的,因此不能在函数中进行修改。
-
如果函数存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数。
-
递归函数未执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型。
-
Scala函数支持可变参数,可变参数需要写在形参列表的最后。
2.递归
object Scala02_Exercise {
def main(args: Array[String]): Unit = {
/*
题1:请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13...给你一个整数n,求出它的斐波那契数是多少?
*/
println("fbn的结果是=" + fbn(7))
/*
题2:求函数值 [演示]已知 f(1)=3; f(n) = 2*f(n-1)+1; 请使用递归的思想编程,求出 f(n)的值
*/
println(f(2))
/*
题3:猴子吃桃子问题有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子?
*/
println("桃子个数=" + peach(1))
}
// 题1:
def fbn(n: Int): Int = {
if (n == 1 || n == 2) {
1
} else {
fbn(n - 1) + fbn(n - 2)
}
}
// 题2:
def f(n: Int): Int = {
if (n == 1) {
3
} else {
2 * f(n - 1) + 1
}
}
// 题3:
def peach(day: Int): Int = {
if (day == 10) {
1
} else {
(peach(day + 1) + 1) * 2
}
}
}
3.过程
将函数的返回类型为Unit的函数称之为过程。如果明确函数没有返回值,那么等号可以省略。
// (1) 定义过程示例1:
def shout1(content: String) : Unit = {
println(content)
}
// (2)定义过程示例2:
def shout1(content: String) = {
println(content)
}
//说明:这只是一个逻辑上的细分,如果因为该概念导致了理解上的混淆,可以暂时直接跳过过程这样的描述。毕竟过程在某种意义上也是函数。
4.惰性函数
惰性计算是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。首先您可以将耗时的计算推迟到绝对需要的时候。其次您可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。Java 并没有为惰性提供原生支持,Scala提供了。
// demo1
object Lazy {
def init(): String = {
println("init方法执行")
"嘿嘿嘿,我来了~"
}
def main(args: Array[String]): Unit = {
lazy val msg = init()
println("lazy方法没有执行")
println(msg)
}
}
//demo2
def main(args: Array[String]): Unit = {
// lazy 不能修饰 var 类型的变量
lazy val res = sum(10, 20)
println("-----------------")
println("res=" + res) //在要使用res 前,才执行 }
def sum(n1: Int, n2: Int): Int = {
println("sum() 执行了..")
return n1 + n2
}
}
5.异常
// Java 异常回顾
public class JavaExceptionDemo01 {
public static void main(String[] args) {
try {
// 可疑代码
int i = 0;
int b = 10;
int c = b / i; // 执行代码时,会抛出ArithmeticException异常
} catch (ArithmeticException ex) {
ex.printStackTrace();
} catch (Exception e) { //java中不可以把返回大的异常写在前,否则报错!!
e.printStackTrace();
} finally {
// 最终要执行的代码
System.out.println("java finally");
}
System.out.println("ok~~~继续执行...");
}
}
// 说明:
// java语言按照try—catch-catch...—finally的方式来处理异常
// 不管有没有异常捕获,都会执行finally, 因此通常可以在finally代码块中释放资源
// 可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示 "Exception 'java.lang.xxxxxx' has already been caught"
// Scala 异常处理
def main(args: Array[String]): Unit = {
try {
val r = 10 / 0
} catch {
//说明
//1. 在scala中只有一个catch
//2. 在catch中有多个case, 每个case可以匹配一种异常 case ex: ArithmeticException
//3. => 关键符号,表示后面是对该异常的处理代码块
//4. finally 最终要执行的
case ex: ArithmeticException=> {
println("捕获了除数为零的算数异常")
}
case ex: Exception => println("捕获了异常")
} finally {
// 最终要执行的代码
println("scala finally...")
}
println("ok,继续执行~~~~~")
}
// 说明:
// 1. 我们将可疑代码封装在try块中。 在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止。
// 2. Scala的异常的工作机制和Java一样,但是Scala没有“checked(编译期)”异常,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。
// 3. 用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。
// 4. 在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case子句来匹配异常。当匹配上后 => 有多条语句可以换行写,类似 java 的 switch case x: 代码块..
// 5. 异常捕捉的机制与其他语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在scala中也不会报错,但这样是非常不好的编程风格。
// 6. finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样。
// 7. Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息。 它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在scala中,可以使用throws注释来声明异常