Scala编程语言近来抓住了很多开发者的眼球。如果你粗略浏览Scala的网站,你会觉得Scala是一种纯粹的面向对象编程语言,而又无缝地结合了命令式和函数式的编程风格。
Christopher Diggins认为:不太久之前编程语言还可以毫无疑义地归类成“命令式”或者“函数式”。Scala代表了一个新的语言品种,它抹平了这些人为划分的界限。
根据David Rupp在博客中的说法,Scala可能是下下一代Java。这么高的评价让人不禁想看看它到底是什么东西。
一种可伸缩语言
Scala 的名称表明,它还是一种高度可伸缩的语言。
Scala 是一种函数对象混合的语言,具有一些强大的优点:
◆ 首先,Scala 可编译为 Java 字节码,这意味着它在 JVM 上运行。除了允许继续利用丰富的 Java 开源生态系统之外,Scala 还可以集成到现有的 IT 环境中,无需进行迁移。
◆ 其次,Scala 基于 Haskell 和 ML 的函数原则,大量借鉴了 Java 程序员钟爱的面向对象概念。因此,它可以将两个领域的优势混合在一起,从而提供了显著的优点,而且不会失去我们一直依赖的熟悉的技术。
◆ 最后,Scala 由 Martin Odersky 开发,他可能是 Java 社区中研究 Pizza 和 GJ 语言的最著名的人,GJ
是 Java 5 泛型的工作原型。而且,它给人一种 “严肃” 的感觉;该语言并不是一时兴起而创建的,它也不会以同样的方式被抛弃。
Scala有几项关键特性表明了它的面向对象的本质。例如,Scala中的每个值都是一个对象,包括基本数据类型(即布尔值、数字等)在内,连函数
也是对象。另外,类可以被子类化,而且Scala还提供了基于mixin的组合(mixin-based composition)。
与只支持单继承的语言相比,Scala具有更广泛意义上的类重用。Scala允许定义新类的时候重用“一个类中新增的成员定义(即相较于其父类的差异之处)”。Scala称之为mixin类组合。
Scala还包含了若干函数式语言的关键概念,包括高阶函数(Higher-Order Function)、局部套用(Currying)、嵌套函数(Nested Function)、序列解读(Sequence Comprehensions)等等。
Scala中常用的一些设计模式:
◆ Singleton(单体模式)
◆ Strategy(策略模式)
◆ Factory(工厂模式)
◆ Vistor(访问者模式)
◆ Decorator(装饰模式)
与许多最近开始在 Java 平台上占有一席之地的动态语言不同,Scala
是静态类型的,这就允许它提供泛型类、内部类、甚至多态方法(Polymorphic Method),正如 Java 代码一样。但是,与 Java
平台不同,Scala 大量利用了类型推断(type
inferencing),这意味着,编译器深入分析代码以确定特定值的类型,无需编程人员干预。类型推断需要较少的冗余类型代码。另外值得一提的
是,Scala被特意设计成能够与Java和.NET互操作。Scala当前版本还不能在.NET上运行(虽然上一版可以-_-b),但按照计划将来可以
在.NET上运行。
大量的其他函数功能(比如模式匹配)已经被引入到 Scala 语言中,但是将其全部列出超出了本文的范围。Scala 还添加许多目前 Java
编程中没有的功能,比如操作符重载(它完全不像大多数 Java 开发人员所想象的那样), 具有 “更高和更低类型边界”
的泛型、视图等。与其他功能相比,这些功能使得 Scala 在处理特定任务方面极其强大,比如处理或生成 XML。
Scala可以与Java互操作。它用scalac这个编译器把源文件编译成Java的class文件(即在JVM上运行的字节码)。你可以从
Scala中调用所有的Java类库,也同样可以从Java应用程序中调用Scala的代码。用David
Rupp的话来说,它也可以访问现存的数之不尽的Java类库,这让(潜在地)迁移到Scala更加容易。
这让Scala得以使用为Java1.4、5.0或者6.0编写的巨量的Java类库和框架,Scala会经常性地针对这几个版本的Java进行测
试。Scala可能也可以在更早版本的Java上运行,但没有经过正式的测试。Scala以BSD许可发布,并且数年前就已经被认为相当稳定了。
说了这么多,我们还没有回答一个问题:“为什么我要使用Scala?”Scala的设计始终贯穿着一个理念:
创造一种更好地支持组件的语言。(《The Scala Programming Language》,Donna Malayeri)
也就是说软件应该由可重用的部件构造而成。Scala旨在提供一种编程语言,能够统一和一般化分别来自面向对象和函数式两种不同风格的关键概念。
藉着这个目标与设计,Scala得以提供一些出众的特性,包括:
◆ 面向对象风格
◆ 函数式风格
◆ 更高层的并发模型
Scala把Erlang风格的基于actor的并发带进了JVM。开发者现在可以利用Scala的actor模型在JVM上设计具伸缩性的并发应用程序,它会自动获得多核心处理器带来的优势,而不必依照复杂的Java线程模型来编写程序。
◆ 轻量级的函数语法
a. 高阶
b. 嵌套
c. 局部套用(Currying)
d. 匿名
◆ 与XML集成
a. 可在Scala程序中直接书写XML
b. 可将XML转换成Scala类
◆ 与Java无缝地互操作
Scala的风格和特性已经吸引了大量的开发者,比如Debasish Ghosh就觉得:我已经把玩了Scala好一阵子,可以说我绝对享受这个语言的创新之处。
总而言之,Scala是一种函数式面向对象语言,它融汇了许多前所未有的特性,而同时又运行于JVM之上。随着开发者对Scala的兴趣日增,以及越来越多的工具支持,无疑Scala语言将成为你手上一件必不可少的工具。
=============================================================================
Scala 是一种有趣的语言。它一方面吸收继承了多种语言中的优秀特性,一方面又没有抛弃 Java 这个强大的平台,它运行在 Java
虚拟机 (Java Virtual Machine) 之上,轻松实现和丰富的 Java
类库互联互通。它既支持面向对象的编程方式,又支持函数式编程。它写出的程序像动态语言一样简洁,但事实上它确是严格意义上的静态语言。Scala
就像一位武林中的集大成者,将过去几十年计算机语言发展历史中的精萃集于一身,化繁为简,为程序员们提供了一种新的选择。作者希望通过这个系列,可以为大
家介绍 Scala 语言的特性,和 Scala 语言给我们带来的关于编程思想的新的思考。
在本系列的第一篇文章 《使用递归的方式去思考》中,
作者并没有首先介绍 Scala
的语法,这样做有两个原因:一是因为过多的陷入语法的细节当中,会分散读者的注意力,反而忽略了对于基本概念,基本思想的理解;二是因为 Scala
语法非常简洁,拥有其他语言编程经验的程序员很容易读懂 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. 定义变量
val x = 0 var y = 1 y = 2 // 给常量赋值会出现编译错误 // x = 3 // 显式指定变量类型 val x1: Int = 0 var y1: Int = 0
仔细观察上述代码,我们会有两个发现:
定义变量时没有指定变量类型。这是否意味着 Scala 是和 Python 或者 Ruby 一样的动态类型语言呢?恰恰相反,Scala 是严格意义上的静态类型语言,由于其采用了先进的类型推断(Type Inference)技术,程序员不需要在写程序时显式指定类型,编译器会根据上下文推断出类型信息。比如变量 x
被赋值为 0,0 是一个整型,所以 x
的类型被推断出为整型。当然,Scala 语言也允许显示指定类型,如变量 x1
,y1
的定义。一般情况下,我们应尽量使用 Scala 提供的类型推断系统使代码看上去更加简洁。
另一个发现是程序语句结尾没有分号,这也是 Scala 中约定俗成的编程习惯。大多数情况下分号都是可省的,如果你需要将两条语句写在同一行,则需要用分号分开它们。
函数的定义也非常简单,使用关键字 def
,
后跟函数名和参数列表,如果不是递归函数可以选择省略函数返回类型。Scala
还支持定义匿名函数,匿名函数由参数列表,箭头连接符和函数体组成。函数在 Scala
中属于一级对象,它可以作为参数传递给其他函数,可以作为另一个函数的返回值,或者赋给一个变量。在下面的示例代码中,定义的匿名函数被赋给变量 cube
。匿名函数使用起来非常方便,比如 List
对象中的一些方法需要传入一个简单的函数作为参数,我们当然可以定义一个函数,然后再传给 List
对象中的方法,但使用匿名函数,程序看上去更加简洁。
清单 2. 定义函数
// 定义函数 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. 定义内部函数
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 表达式
def abs(n: Int): Int = if (n > 0) n else -n
和 Java 一样,Scala 提供了用于循环的 while 语句,在下面的例子中,我们将借助 while 循环为整数列表求和。
清单 5. 使用 while 为列表求和
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. 使用递归对数列求和
//xs.head 返回列表里的头元素,即第一个元素 //xs.tail 返回除头元素外的剩余元素组成的列表 def sum1(xs: List[Int]): Int = if (xs.isEmpty) 0 else xs.head + sum1(xs.tail)
有没有更简便的方式呢?答案是肯定的,我们可以使用列表内置的一些方法达到同样的效果:
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 程序,首先我们定义这个迭代过程,这也是该算法的核心部分,所幸这一算法非常简单,利用递归,一个 if else
表达式就能搞定。后续为两个辅助方法,让我们的程序看起来更加清晰。最后我们选定初始假设为 1
,定义出最终的 sqrt
方法。
清单 7. 使用牛顿法求解平方根
// 迭代函数,若解不满足精度,通过递归调用接续迭代 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. 使用牛顿法求解平方根 – 使用内部函数隐藏细节
// 目标函数,通过将需要用到的辅助函数定义为内部函数,实现细节的隐藏 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
println(“Hello Script!”)
作为应用程序执行
作为应用程序执行时,我们需要在一个单例对象中定义入口函数 main
,经过编译后就可以执行该应用程序了。
清单 10. HelloWorld.scala
object HelloWorld { def main(args: Array[String]): Unit = { println("Hello World!") } }
Scala 还提供了一个更简便的方式,直接继承另一个对象 App,无需定义 main
方法,编译即可运行。
清单 11. HelloScala.scala
object HelloScala extends App { println("Hello Scala!") }
结束语
本
文为大家介绍了 Scala 的基本语法,相比 Java,Scala 的语法更加简洁,比如 Scala
的类型推断可以省略程序中绝大多数的类型声明,短小精悍的匿名函数可以方便的在函数之间传递,还有各种在 Scala
社区约定俗成的习惯,比如省略的分号以及函数体只有一条表达式时的花括号,这一切都帮助程序员写出更简洁,更优雅的程序。限于篇幅,本文只介绍了
Scala 最基本的语法,如果读者想跟进一步学习 Scala,请参考 Scala 的 官方文档及文末所附的参考资源。
掌握了这些基本的语法,我们将在下一篇文章中为大家介绍如何使用 Scala 进行函数式编程,这是 Scala 最让人心动的特性之一,对于习惯了面向对象的程序员来说,学习 Scala 更多的是在学习如何使用 Scala 进行函数式编程。