在本系列的第一篇文章 《使用递归的方式去思考》中,作者并没有首先介绍 Scala 的语法,这样做有两个原因:一是由于过多的陷入语法的细节其中,会分散读者的注意力。反而忽略了对于基本概念,基本思想的理解。二是由于 Scala 语法非常简洁,拥有其它语言编程经验的程序猿非常easy读懂 Scala 代码。如今我们将回过头来。从基本的语法開始学习 Scala 语言。大家会发现 Scala 语言异常精炼,实现相同功能的程序。在代码量上,使用 Scala 实现通常比 Java 实现少一半或者很多其它。短小精悍的代码经常意味着更易懂,更易维护。本文将为大家介绍 Scala 语言的基本的语法,帮助大家写出自己的第一个 Scala 程序。
开发环境
学习 Scala。最方便的方式是安装一个 Scala 的 IDE(集成开发环境),Typesafe 公司开发了一款基于 Eclipse 的 IDE。该款 IDE 为 Scala 刚開始学习的人提供了一个方便的功能:Worksheet。
像 Python 或者 Ruby 提供的 RELP(Read-Eval-Print Loop)一样,Worksheet 同意用户输入 Scala 表达式。保存后马上得到程序执行结果,很方便用户体验 Scala 语言的各种特性。怎样安装 Scala IDE 和使用 Worksheet。请大家參考 https://class.coursera.org/progfun-002/wiki/view?page=ToolsSetup。
Hello World
让我们以经典的 Hello World 程序開始,仅仅需在 Worksheet 里输入 println("Hello World!")
保存就可以,在该语句的右边就能够立马看到程序运行结果。
Worksheet 也能够被当作一个简单的计算器,试着输入一些算式。保存。
图 1. Worksheet
变量和函数
当然。我们不能只满足使用 Scala 来进行一些算术运算。写略微复杂一点的程序,我们就须要定义变量和函数。Scala 为定义变量提供了两种语法。
使用 val
定义常量,一经定义后。该变量名不能被又一次赋值。使用 var
定义变量,可被又一次赋值。
在
Scala 中。鼓舞使用 val
,除非你有明白的需求使用 var
。
对于
Java 程序猿来说。刚開始可能会认为有违直觉。但习惯后你会发现,大多数场合下我们都不须要 var
。一个可变的变量。
清单 1. 定义变量
1
2
3
4
5
6
7
8
9
10
|
val
x =
0 var
y =
1 y
=
2 //
给常量赋值会出现编译错误 //
x = 3 //
显式指定变量类型 val
x 1 :
Int =
0 var
y 1 :
Int =
0 |
细致观察上述代码,我们会有两个发现:
定义变量时没有指定变量类型。
这是否意味着 Scala 是和 Python 或者 Ruby 一样的动态类型语言呢?恰恰相反,Scala 是严格意义上的静态类型语言,因为其採用了先进的类型判断(Type Inference)技术。程序猿不须要在敲代码时显式指定类型,编译器会依据上下文判断出类型信息。
比方变量 x
被赋值为 0,0 是一个整型。所以 x
的类型被判断出为整型。当然,Scala
语言也同意显示指定类型。如变量 x1
。y1
的定义。
普通情况下。我们应尽量使用 Scala 提供的类型判断系统使代码看上去更加简洁。
还有一个发现是程序语句结尾没有分号,这也是 Scala 中约定俗成的编程习惯。
大多数情况下分号都是可省的。假设你须要将两条语句写在同一行,则须要用分号分开它们。
函数的定义也很easy。使用keyword def
,后跟函数名和參数列表,假设不是递归函数能够选择省略函数返回类型。Scala 还支持定义匿名函数,匿名函数由參数列表。箭头连接符和函数体组成。函数在 Scala 中属于一级对象,它能够作为參数传递给其它函数,能够作为还有一个函数的返回值,或者赋给一个变量。
在以下的演示样例代码中。定义的匿名函数被赋给变量 cube
。
匿名函数使用起来很方便。比方 List
对象中的一些方法须要传入一个简单的函数作为參数。我们当然能够定义一个函数。然后再传给 List
对象中的方法,但使用匿名函数。程序看上去更加简洁。
清单 2. 定义函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//
定义函数 def
square(x :
Int) :
Int = x
* x //
假设不是递归函数。函数返回类型可省略 def
sum _ of _ square(x :
Int, y :
Int) = square(x)
+ square(y) sum _ of _ square( 2 ,
3 )
//
定义匿名函数 val
cube =
(x :
Int) = >
x * x *x cube( 3 )
//
使用匿名函数,返回列表中的正数 List(- 2 ,
- 1 ,
0 ,
1 ,
2 ,
3 ).filter(x
= >
x > 0 ) |
让我们再来和 Java 中相应的函数定义语法比較一下。
首先,函数体没有像 Java 那样放在 {}
里。
Scala 中的一条语句事实上是一个表达式。函数的运行过程就是对函数体内的表达式的求值过程,最后一条表达式的值就是函数的返回值。假设函数体仅仅包括一条表达式,则能够省略 {}
。其次,没有显示的 return
语句,最后一条表达式的值会自己主动返回给函数的调用者。
和 Java 不同,在 Scala 中,函数内部还能够定义其它函数。比方上面的程序中,假设用户仅仅对 sum_of_square 函数感兴趣,则我们能够将 square 函数定义为内部函数,实现细节的隐藏。
清单 3. 定义内部函数
1
2
3
4
5
|
def
sum _ of _ square(x :
Int, y :
Int) :
Int =
{ def
square(x :
Int) = x
* x square(x)
+ square(y) } |
流程控制语句
复杂一点的程序离不开流程控制语句。Scala 提供了用于条件推断的 if else
和表示循环的 while
。和
Java 中相应的条件推断语句不同,Scala 中的 if else
是一个表达式。依据条件的不同返回相应分支上的值。比方以下样例中求绝对值的程序,因为 Scala 中的 if
else
是一个表达式,所以不用像 Java 那样显式使用 return
返回对应的值。
清单 4. 使用 if else 表达式
1
2
|
def
abs(n :
Int) :
Int = if
(n > 0 )
n else
-n |
和 Java 一样,Scala 提供了用于循环的 while 语句。在以下的样例中。我们将借助 while 循环为整数列表求和。
清单 5. 使用 while 为列表求和
1
2
3
4
5
6
7
8
9
|
def
sum(xs :
List[Int]) =
{ var
total =
0 var
index =
0 while
(index < xs.size) { total
+ =
xs(index) index
+ =
1 }
total
} |
上述程序是习惯了 Java 或 C++ 的程序猿想到的第一方案,但细致观察会发现有几个问题:首先,使用了 var
定义变量,我们在前面说过,尽量避免使用 var
。其次,这个程序太长了。第一次拿到这个程序的人须要对着程序细致端详一会:程序首先定义了两个变量,并将其初始化为 0
。然后在 index
小于列表长度时运行循环。在循环体中。累加列表中的元素,并将 index
加 1
。最后返回终于的累加值。直到这时。这个人才意识到这个程序是对一个数列求和。
让我们换个角度,尝试用递归的方式去思考这个问题,对一个数列的求和问题能够简化为该数列的第一个元素加上由兴许元素组成的数列的和。依此类推,直到兴许元素组成的数列为空返回 0。
详细程序例如以下,使用递归,原来须要 9 行实现的程序如今仅仅须要两行,并且程序逻辑看起来更清晰。更易懂。
(关于怎样使用递归的方式去思考问题,请參考作者的另外一篇文章《使用递归的方式去思考》)
清单 6. 使用递归对数列求和
1
2
3
4
|
//xs.head
返回列表里的头元素,即第一个元素 //xs.tail
返回除头元素外的剩余元素组成的列表 def
sum 1 (xs :
List[Int]) :
Int = if
(xs.isEmpty) 0
else
xs.head + sum 1 (xs.tail) |
有没有更简便的方式呢?答案是肯定的,我们能够使用列表内置的一些方法达到相同的效果:
1
|
xs.foldLeft( 0 )((x0,
x) => x0 + x) |
该方法传入一个初始值 0。一个匿名函数,该匿名函数累加列表中的每个元素,终于返回整个列表的和。使用上面的方法,我们甚至不须要定义额外的方法,就行完毕相同的操作。其实。List 已经为我们提供了 sum 方法。在实际应用中。我们应该使用该方法,而不是自定义一个。作者仅仅是希望通过上述样例。让大家意识到 Scala 尽管提供了用于循环的 while 语句,但大多数情况下,我们有其它更简便的方式可以达到相同的效果。
使用牛顿法求解平方根
掌握了上面这些内容,我们已经能够利用 Scala 求解非常多复杂的问题了。
比方我们能够利用牛顿法定义一个函数来求解平方根。
牛顿法求解平方根的基本思路例如以下:给定一个数 x
,可如果其平方根为随意一个正数 ( 在这里,我们选定 1 为初始的如果 ),然后比較 x
与该数的平方。如果两者足够近似(比方两者的差值小于
0.0001),则该正数即为 x
的平方根;否则又一次调整如果。如果新的平方根为上次如果
与 x/
上次如果
的和的平均数。
通过下表能够看到。经过只 4 次迭代,就能求解出相当精确的 2 的平方根。
表 1. 牛顿法求解 2 的平方根
如果 | 如果的平方与 2 进行比較 | 新的如果 |
---|---|---|
1 | |1 * 1 – 2| = 1 | (1 + 2/1)/2 = 1.5 |
1.5 | |1.5 * 1.5 – 2| = 0.25 | (1.5 + 2/1.5)/2 = 1.4167 |
1.4167 | |1.4167 * 1.4167 – 2| = 0.0070 | (1.4167 + 2/1.4167)/2 = 1.4142 |
1.4142 | |1.4142 * 1.4142 – 2| = 0.000038 | …… |
将上述算法转化为 Scala 程序,首先我们定义这个迭代过程,这也是该算法的核心部分,所幸这一算法很easy,利用递归。一个 if else
表达式就能搞定。
兴许为两个辅助方法,让我们的程序看起来更加清晰。最后我们选定初始如果为 1
,定义出终于的 sqrt
方法。
清单 7. 使用牛顿法求解平方根
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//
迭代函数,若解不满足精度,通过递归调用接续迭代 def
sqrtIter(guess :
Double, x :
Double) :
Double = if
(isGoodEnough(guess, x)) guess
else sqrtIter((guess
+ x / guess)/ 2 ,
x) //
推断解是否满足要求 def
isGoodEnough(guess :
Double, x :
Double) = abs(guess
* guess - x)< 0.0001 //
辅助函数。求绝对值 def
abs(x :
Double) = if
(x < 0 )
-x else
x //
目标函数 def
sqrt(x :
Double) :
Double = sqrtIter( 1 ,
x) //
測试代码 sqrt( 2 ) |
这段程序看起来相当优美:首先它没有使用 var
定义其它辅助变量。在程序中避免使用 var
总是一件好事情。其次它没有使用 while
循环描写叙述整个迭代过程,取而代之的是一段很简洁的递归,使程序逻辑上看起来更加清晰。最后它没有将整个逻辑所有塞到一个函数里,而是分散到不同的函数里,每一个函数各司其职。然而这段程序也有一个显而易见的缺陷,作为用户。他们仅仅关心 sqrt
函数,但这段程序却将其它一些辅助函数也暴露给了用户。我们在前面提到过,Scala
里能够嵌套定义函数,我们能够将这些辅助函数定义为sqrt
的内部函数,更进一步,因为内部函数能够訪问其函数体外部定义的变量,我们能够去掉这些辅助函数中的 x
參数。终于的程序例如以下:
清单 8. 使用牛顿法求解平方根 – 使用内部函数隐藏细节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//
目标函数,通过将须要用到的辅助函数定义为内部函数,实现细节的隐藏 def
sqrt(x :
Double) :
Double =
{ //
迭代函数,若解不满足精度。通过递归调用接续迭代 def
sqrtIter(guess :
Double) :
Double = if
(isGoodEnough(guess)) guess
else sqrtIter((guess
+ x / guess) / 2 )
//
推断解是否满足要求 def
isGoodEnough(guess :
Double) = abs(guess
* guess - x) < 0.0001 //
辅助函数,求绝对值 def
abs(x :
Double) = if
(x < 0 )
-x else
x sqrtIter( 1 )
} |
怎样执行 Scala 程序
我们已经利用 Scala 集成开发环境提供的 Worksheet 体验了 Scala 的基本的语法。在实际开发中,我们更关心怎样执行 Scala 程序。在执行方式上,Scala 重新体现出了它的灵活性。
它能够被当作一种脚本语言执行,也能够像 Java 一样,作为应用程序执行。
作为脚本运行
我们能够将 Scala 表达式写在一个文件中,比方 Hello.scala。在命令行中直接输入 scala Hello.scala
就可得到程序执行结果。
清单 9. Hello.scala
1
|
println(“Hello
Script!”) |
作为应用程序运行
作为应用程序运行时。我们须要在一个单例对象中定义入口函数 main
,经过编译后就能够运行该应用程序了。
清单 10. HelloWorld.scala
1
2
3
4
5
|
object
HelloWorld { def
main(args :
Array[String]) :
Unit =
{ println( "Hello
World!" )
}
} |
Scala 还提供了一个更简便的方式。直接继承还有一个对象 App,无需定义 main
方法。编译就可以执行。
清单 11. HelloScala.scala
1
2
3
|
object
HelloScala extends
App { println( "Hello
Scala!" )
} |
结束语
本文为大家介绍了 Scala 的基本的语法。相比 Java,Scala 的语法更加简洁。比方 Scala 的类型判断能够省略程序中绝大多数的类型声明,短小精悍的匿名函数能够方便的在函数之间传递,还有各种在 Scala 社区约定俗成的习惯,比方省略的分号以及函数体仅仅有一条表达式时的花括号,这一切都帮助程序猿写出更简洁,更优雅的程序。限于篇幅,本文仅仅介绍了 Scala 最基本的语法,假设读者想跟进一步学习 Scala。请參考 Scala 的 官方文档及文末所附的參考资源。
掌握了这些主要的语法, 就能够用Scala 进行函数式编程,这是 Scala 最让人心动的特性之中的一个。对于习惯了面向对象的程序猿来说。学习 Scala 很多其它的是在学习怎样使用 Scala 进行函数式编程。