Scala语言的特性

Scalable语言
Scala是一门可伸缩的scalable语言,既可以写复杂的服务器端程序,也可以写简单的脚本
纯正的面向对象
所有的概念最终都会被时限为纯正的对象
函数式编程的特性
函数式程序思想!!!
无缝的Java互操作
构建于Jvm之上,Java的包可以在Scala中使用,huo1Scala写好的程序给Java调用
编程思路灵活
既可以面向对象的思想,也可以函数式编程的思想

Scala之父:Martin Odersky

导读
函数式变成的概念和思想
Scala的开发环境搭建
Scala语言的基础
Scala中的类型和求值策略
Scala中函数的概念
Immutable Collections如何用函数式思想实现数据结构和其上的一些操作

函数式编程思想

只用纯函数编程

定义:函数式编程是一种编程范式,构建计算机程序和结构的方法和风格,把计算当做数学函数求值的过程,并且避免了改变状态和可变的数据

纯函数特点 Pure Function

纯函数,没有副作用的函数

没有副作用:状态的变化

例如:调用 def Add(y:Int) = x + y
其结果为xy之和,并且调用之后没有引起x值的变换,没有副作用
所以,Add函数没有副作用

引用透明性

对于上述Add函数,对于同一输入y,返回结果均相同
所以,Add具有引用透明性

如何确保引用透明

不变性Immutablity:任何的状态和值都是不变的,才能获得引用透明
函数与变量,对象类是同一级的,即函数中可以定义函数,有变量的地方都可以使用函数,都是等同的

高阶函数

函数作为一个函数的输入或另一个函数的输出

闭包 closure

表达式求值

函数式编程中,一切都是表达式,表达式求值策略:

严格求值:call by value
非严格求值:call by name

惰性求值

定义表达式时不会立即求值,只在第一次调用时才求值

递归函数

函数式编程中没有循环语句,全部的循环用递归实现
调优递归:尾递归

函数式编程的优点

Lisp是第一种函数式编程语言

  1. 编程代码量少
  2. 当构造完含数之后,对于相同输入,输出相同,便于调试
  3. 非常适用于并行编程,没有副作用,具备引用透明性,在n个节点运算结果是相同的
  4. 传统语言多核编程非常复杂

Scala环境的搭建

安装Jdk6以上,并安装Scala包

Scala基础语法

变量修饰符

  1. val 定义 immutable variable 常量
  2. var 定义 mutable variable 变量
  3. lazy val 惰性求值的常量

定义时不用显示的说明类型,scala会自己进行变量推导

前两种定义,在定义时表达式就会立即求值
lazy val
在REPL中,scala会给没有变量名的变量自动取值resN,可以直接引用已有的resN
注意:
scala中不允许常量定义后被直接改变,而变量var可以
val x = 10
x = 20 //会报错reassignment to val
val x = 20 //正确的重新赋值,需要使用val重新定义
对于lazy val,注意没有lazy var,一般是定义惰性求值的表达式
val l = 常量或变量组成的表达式

Scala的类体系

Any 所有类的父类
AnyVal 值类型
NumericTypes 数值类型 Byte,Shot,Int,Long,Float,Double
Boolean 布尔类型
Char 字符类型
Unit 空类型,相当于Java的void
AnyRef 所有引用类型的父类
All java. ref types 所有Java的引用类都是其子类
All scala.
ref types 所有自定义的scala的类都是其子类
Null 所有引用类型的最后一个子类
Nothing 所有类型的最后一个子类(既是AnyVal又是AnyRef的子类)

  1. NumericTypes
    对于数值类型:低精度可以赋值给高精度,反之不行,数据会缺失:报类型不匹配错误
  2. Unit
    往往作为函数的返回值出现,表明函数有副作用
  3. Null
    表示一个引用类型的值为空。通常不使用
  4. Nothing
    对于函数而言,如果返回值为nothing,那么则表示函数异常
    scala> def foo() = throw new Exception(“1111”)
    foo: ()Nothing
  5. String
    新特性 - 字符串插值(interpolation)
    scala> val name=”Jack”
    name: String = Jack
    scala> s”my name is $name” //使用字符串插值
    res11: String = my name is Jack

代码块Block

代码块用于组织多个表达式:{exp1;exp2}
多个表达式在一行2时需要分号分割,代码块本事也是一个表达式
最后的求值,是最后一个表达式

函数

定义函数的方式:
def functionName(param:paramType):returnType = {
//body expressions
}
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object worksheetA {
// 完整形式
def Hello(name:String):String = {
s"Hello,$name"
} //> Hello: (name: String)String
Hello("Jack") //> res0: String = Hello,Jack
// 省略返回值,自动推断类型
def Hello2(name:String) = {
s"Hello,$name"
} //> Hello2: (name: String)String
Hello2("Tom") //> res1: String = Hello,Tom
// 单语句的函数体
def add(x:Int,y:Int) = {
x + y
} //> add: (x: Int, y: Int)Int
// 可以省略大扩号
//def add(x:Int,y:Int) = x + y
add(1,2) //> res2: Int = 3
}

scala中的if

if是表达式,而不是语句
if(逻辑表达式) valA else valB
val a = 1 //> a : Int = 1
if(a!=1) “not none” //> res3: Any = ()返回空
if(a!=1) “not none” else a //> res4: Any = 1

scala中的for comprehension

用于实现循环的一种推导式,本身是由map() reduce()组合实现的
是scala语法糖(thin text sugar)的一种

1
2
3
4
5
for{
x <- xs
y = x + 1
if( y > 0)
}yield y

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
object worksheetA {
//初始化一个List
val list = List("alice","bob","cathy")
for (
//遍历list每一个元素给s,generator
s <- list
)println(s)
for {
s <- list
//串长度大于三才被打印
if( s.length > 3)
}println(s)
val res_for = for{
s <- list
//变量绑定,variable binding
s1 = s.toUpperCase()
if ( s1 != "")
//yeild导出的意思,如果每次s1不空,则生成新的collection
}yield (s1)
}

scala中的try

try也是一个表达式,返回一个值

1
2
3
4
5
6
7
try{
Integer.praseInt("dog")
}catch{
case _ => 0 //下划线是通配符,统配所有异常
}finally{
print("总是会打印");
}

scala中的macth

类似switch,但也是一个表达式,返回相应的值,主要用在 pattern match

1
2
3
4
5
6
7
var expression = 1 //> expression : Int = 1
expression match{
case 1 => "dog"
case 2 => "cat"
//类似switch的default
case _ => "others"
} //> res5: String = dog

Scala的求值策略

scala中所有运算都是基于表达式的,求值会有不同策略

  1. call by value

    对函数实参求值,仅求一次,求得的值直接替换函数中的形式参数

  2. call by value

    不会对函数实参进行表达式求值,直接把表达式传入函数体内,替换表达式的形参,然后在函数内每次使用到此形参时会被求值

scala通常使用call by value

def foo(x: Int) = x //call by Value
def foo(x: => Int) = x //call by Name

下面是两种求值策略在不同情况下的运行机制:

1
2
3
4
5
6
7
8
9
10
11
def add(x: Int,y: Int) = x * x def add(x: =>Int,y: =>Int) = x * x
add(3+4,7) add(3+4,7)
=>add(7,7) =>(3+4)*(3+4)
=>7*7 =>7*(3+4)
=>49 =>7*7
=>49
add(7,3+4) add(7,3+4)
=>add(7,7) =>7*7
=>7*7 =>49
=>49

注意上述运行机制的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala> def bar(x:Int, y: => Int) : Int = 1
bar: (x: Int, y: => Int)Int
scala> def loop():Int = loop
loop: ()Int
scala> bar(1,loop)
//loop函数位于的参数的定义方式是y: => Int,即call by name,不进行求值,会带到函数体内并且使用时
才求值,此处,loop没有机会执行。
res0: Int = 1
scala> bar(loop,1)
//loop函数位于的参数的定义方式是y: Int,即call by value,会直接将表达式求值并代替形参,此处loop
首先被执行求值,故而陷入死循环。
输出:死循环

进行函数设计和调用时,两种差异要搞清楚

Scala中的函数

  1. 支持把函数作为实参传递给另外一个函数
  2. 支持把函数作为返回值
  3. 支持把函数赋值给变量
  4. 支持把函数存储在数据结构里

即,在scala中,函数跟普通变量一样使用,且具有函数的相关类型

函数的类型

在scala中,函数类型的格式为 A => B,表示一个:接受参数类型为A的、并返回类型B的函数
eg:
Int => String 是把整型映射为字符串的函数类型

高阶函数

  1. 接受的参数为函数
    1
    2
    3
    def funcName( f: (Int, Int) => Int) = {
    f(4,4)
    }

参数:f
类型:Int => Int
返回:Int类型

  1. 返回值为一个函数
    1
    def funcName() = ( name: String) => {"hello "+name}

参数:name
类型:String => String
返回:String类型
注意上述叫做:匿名函数 - 函数常量 - 函数的文字量(相对于def funcName 叫函数变量)

  1. 兼具上述情况

匿名函数

匿名函数没有函数名
定义格式: (形参列表) => { 函数体 }

  • (x: Int) => { x * x }
  • (x: Int,y: Int) => { x + y }
  • var add = (x: Int,y: Int) => { x + y }
  • def func() = (x: Int,y: Int) => { x + y }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    scala> (x: Int,y: Int) => { x + y }
    res0: (Int, Int) => Int = $$Lambda$1016/2093139281@69feb4d9
    scala> var add = (x: Int,y: Int) => { x + y }
    add: (Int, Int) => Int = $$Lambda$1025/1152113439@62108cd3
    scala> add(1,2)
    res1: Int = 3
    scala> def funcName() = ( name: String) => {"hello "+name}
    funcName: ()String => String
    scala> funcName()("Jack")
    res4: String = hello Jack

柯里化

Scala中的重要的技术,具有多个参数的函数转化成一个函数列,每个函数只有单一参数

1
2
3
4
5
6
7
def add(x: Int,y: Int) = { x + y } //普通函数定义
def add(x: Int)(y: Int) = { x + y } //柯里化函数定义,多个参数单一化,串接起来
def curriedAdd(a: Int)(b: Int) = a + b
curriedAdd(2)(2) //4
val add = curriedAdd(1)_ //Int => Int
add(2) //3

解释:curriedAdd(1)_,下划线统配之后的全部参数列表,此处a=1固定,只有b是可变值,下划线通配变量b
add(2),传入curriedAdd后a=1,b=2

利用柯里化技术,通过原有通用函数构造一些新的函数

Scala中的递归

scala里计算n的阶乘

1
2
3
def factorial(n: Int): Int =
if(n <= 0) 1
else n * factorial(n - 1)

递归优化:变成尾递归,尾递归会复写当前栈,不会导致堆栈溢出
尾递归优化:用¥annotation.tailrec显示指明编译时进行尾递归优化

1
2
3
4
5
@annotation.tailrec
def factorial(n: Int,m: Int): Int =
if(n <= 0) m
else factorial(n - 1, m * n)
factorial(5,1)

上述引入m,m保留当前运算之前的历史阶乘结果
如果退出,则就是递归的值,如果不退出,那么把当前结果传入下一次,这样不需要开辟栈保留计算结果,每次只需m变量记录结果
示例:求f(x)在(a,b)上的和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def sum(f: Int => Int)(a: Int)(b: Int): Int = {
@annotation.tailrec
def loop(n: Int, acc: Int): Int = {
//函数中可定义其他函数
//n:循环变量
//acc:累积和
if (n > b) {
println(s"n=${n}, acc=${acc}")
acc
//if表达式返回acc的值
} else {
println(s"n=${n}, acc=${acc}")
loop(n + 1, acc + f(n))
//else表达式返回loop
}
}
loop(a, 0)
} //> sum: (f: Int => Int)(a: Int)(b: Int)Int
//函数y=f(x)
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=0
//| n=2, acc=1
//| n=3, 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=0
//| n=2, acc=1
//| n=3, acc=9
//| n=4, acc=36
//| n=5, acc=100
//| n=6, acc=225
//| res2: Int = 225
//利用柯里化技术,通过原有通用函数构造一些新的函数,简化代码
val sumSquare = sum(x => x * x)_
//> sumSquare : Int => (Int => Int) = sumfunc$$$Lambda$13/2054798982@34ce8af7
sumSquare(1)(5) //> n=1, acc=0
//| n=2, acc=1
//| n=3, acc=5
//| n=4, acc=14
//| n=5, acc=30
//| n=6, acc=55
//| res3: Int = 55