Warning! Your browser doesn't support the features required.

For the best experience please use the latest Chrome, Safari or Firefox browser.

Scala 指南

开始精彩的Scala旅程

Hello World

Scala 是一门 函数式面向对象语言。他运行在Java虚拟机上.

本指南是用来介绍Scala强大的功能。同时你可以亲身实践他们。

点击左边的Run按钮试试。左边的运行框可以在远程编译和运行任何Scala代码。运行的结果将展示在下面对文本框中。你也可以试着编辑这些代码,重新运行。(初次运行可能会有些慢,不过第二次就很快了)

使用方向键或者空格来翻页

基础开始

目录(基础)

表达式和值

在Scala中,几乎所有的语言元素都是表达式。

println("hello wolrd")

是一个表达式,

"hello"+" world"

也是一个表达式。

可以通过val定义一个常量,亦可以通过var定义一个变量。推荐多使用常量。

函数是一等公民

可以使用def来定义一个函数。函数体是一个表达式。

使用Block表达式的时候,默认最后一行的返回是返回值,无需显式指定。

函数还可以像值一样,赋值给var或val。因此函数也可以作为参数传给另一个函数。

借贷模式

由于函数可以像值一样作为参数传递,所以可以方便的实现借贷模式。

这个例子是从/proc/self/stat文件中读取当前进程的pid。

withScanner封装了try-finally块,所以调用者不用再close

注:当表达式没有返回值时,默认返回Unit。

按名称传递参数

这个例子演示了按名称传递参数,由于有除以0,所以运行该程序会产生异常。

试着将

def log(msg: String)

修改为

def log(msg: => String)

由按值传递修改为按名称传递后将不会产生异常。

因为log函数的参数是按名称传递,参数会等到实际使用的时候才会计算,所以被跳过。

按名称传递参数可以减少不必要的计算和异常。

定义类

可以用class关键字来定义类。并通过new来创建类。

在定义类时可以定义字段,如firstName,lastName。这样做还可以自动生成构造函数。

可以在类中通过def定义函数var和val定义字段

函数名是任何字符如+,-,*,/。

试着将

obama.age_=(51)

简化为

obama.age = 51

这样的简化更像调用一个变量。

鸭子类型

走起来像鸭子,叫起来像鸭子,就是鸭子。

这个例子中使用

{ def close(): Unit }

作为参数类型。因此任何含有close()的函数的类都可以作为参数。

不必使用继承这种不够灵活的特性。

柯里化

这个例子和上面的功能相同。不同的是使用了柯里化(Currying)技术。

def add(x:Int, y:Int) = x + y

是普通的函数

def add(x:Int) = (y:Int) => x + y

是柯里化后的函数,相当于返回一个匿名函数表达式。

 def add(x:Int)(y:Int) = x + y

是简化写法

柯里化可以让我们构造出更像原生语言提供的功能的代码

试着将例子中的withclose(...)(...)换成withclose(...){...}

范型

之前的例子可以使用泛型变得更简洁更灵活。

试着将

"123456"

修改为

123456

虽然msg由String类型变为Int类型,但是由于使用了泛型,代码依旧可以正常运行。

Traits

Traits就像是有函数体的Interface。使用with关键字来混入。

这个例子是给java.util.ArrayList添加了foreach的功能。

试着再在with ForEachAble[Int]后面加上

with JsonAble

给list添加toJson的能力

函数式开始

目录(函数式)

模式匹配

模式匹配是类似switch-case特性,但更加灵活;也类似if-else,但更加简约。

这个例子展示的使用用模式匹配实现斐波那契。 使用case来匹配参数,如果case _,则可以匹配任何参数。

试着将

case n: Int

修改为

case n: Int if (n > 1)

case后添加if语句判断,这样修改当输入负数时,就不会无限循环。

模式匹配也可以匹配类型,在case _ 前加上

case n: String => fibonacci(n.toInt)

这样就可以匹配String类型

Case Class

case class 顾名思义就是为case语句专门设计的类, 在普通类的基础上添加了和类名一直的工厂方法, 还添加了hashcode,equals和toString等方法。

由于使用了require(n >= 0)来检验参数,如果使用负数计算,将会抛出异常。

函数式的威力

这个例子是用指令式编程判断一个List中是否含有奇数。

试着将

containsOdd(list)

替换为

list.exists((x: Int) => x % 2 ==1)

通过将函数作为参数,可以使程序更简洁。还可以再简化为

list.exists(_ % 2 == 1)

可以用_替代参数

相比于Ruby等动态语言,这威力来自于科学而不是魔法

函数式真正的威力

函数式除了能简化代码外,更重要的是他关注的是InputOutput,函数本身没有副作用。

就是Unix pipeline一样,简单的命令组合在一起威力无穷。

如果你喜欢Unix pipeline的方式,你一定也会喜欢函数式编程。

这个例子是用函数式的代码模拟

cat file | grep 'warn' | grep '2013' | wc

List的filter方法接受一个过滤函数,返回一个新的List,作为下一个方法的输入。

Word Count

Word Count是一个MapReduce的一个经典示例。在函数式编程中,Word Count最直观的实现方法也是MapReduce。

这个例子介绍了List的两个重要的高阶方法mapreduceLeft

map接受一个转换函数,返回转换结果。

reduceLeft接受一个合并函数,依次遍历合并。

使用高阶方法可以代替大部分需要循环的操作,使代码更清晰

尾递归

尾递归是递归的一种,特点在于会在函数的最末调用自身。尾递归是函数式编程的常见写法。

这个例子是foldLeft的尾递归实现。foldLeft和reduceLeft相比更常用,多一个初始参数。

当用List做match case时。可以使用 :: 来解构。返回第一个元素head和剩余元素tail。

注:尾递归会在编译期优化,因此不用担心递归造成的栈溢出问题。

更强大的For循环

循环语句是指令式编程的常见语句,Scala对其加以改进,成为适应函数式风格的利器。

For循环也是有返回值的,返回的是一个List。在每一轮迭代中加入yield,yield后的值可以加入到List中。

这个例子是使用for循环代替map函数。

Option

Scala提供了Option机制来解决,代码中不断检查null的问题。

这个例子包装了getProperty方法,使其返回一个Option。 这样就可以不再漫无目的地null检查。只要Option类型的值即可。

使用pattern match来检查是常见做法。也可以使用getOrElse来提供当为None时的默认值。

给力的是Option还可以看作是最大长度为1的List,List的强大功能都可以使用。

再见 NullException

Lazy初始化

Lazy可以延迟初始化字段。加上lazy的字段会在第一次访问的时候初始化,而不是类初始化的时候初始化。

这个例子是从github获得Scala的版本号,由于访问网络需要较多时间。可以使用lazy来延迟获取。 防止可能的浪费。

Lazy非常适合于初始化非常耗时的场景

并发开始

目录(并发)

使用Actor

Actor是Scala的并发模型。在2.10之后的版本中,使用Akka作为其推荐Actor实现。

Actor是类似线程的实体,有一个邮箱。 Actor可以通过system.actorOf来创建,receive获取邮箱消息,向邮箱发送消息。

这个例子是一个EchoServer,接受信息并打印。

Actor更简化的用法

可以通过更简化的办法声明Actor。

通过

akka.actor.ActorDSL

中的actor函数。这个函数可以接受一个Actor的构造器Act,启动并返回Actor。

Actor原理

Actor比线程轻量。在Scala中可以创建数以百万级的Actor。奥秘在于Actor直接可以复用线程

Actor和线程是不同的抽象,他们的对应关系是由Dispatcher决定的。

这个例子创建4个Actor,每次调用的时候打印自身线程名称。

可以发现Actor和线程之间没有一对一的对应关系。一个Actor可以使用多个线程,一个线程也会被多个Actor复用。

同步返回

Actor非常适合于较耗时的操作。比如获取网络资源。

这个例子通过调用ask函数来获取一个Future。

在Actor内部通过 sender ! 传递结果。

Future像Option一样有很多高阶方法,可以使用foreach查看结果。

异步返回

异步操作可以最大发挥效能。Scala的Futrue很强大,可以异步返回。

可以实现Futrue的onComplete方法。当Futrue结束的时候就会回调。

在调用ask的时候,可以设定超时。

并行集合

这个例子是访问若干URL,并记录时间。如果能并行访问,就可以大幅提高性能。

尝试将

urls.map

修改为

urls.par.map

这样每个map中的函数都可以并发执行。

当函数式和并发结合,就会这样让人兴奋。

并行wordcount

这个例子是访问若干URL,并记录时间。

并行集合支持大部分集合的功能。

在前面有一个wordcount例子,也可以用并行集合加以实现。

不增加程序复杂性,却能大幅提高利用多核的能力。

远程Actor

Actor是并发模型,也使用于分布式。

这个例子创建一个Echo服务器,通过actorOf来注册自己。

然后再创建一个client,通过akka url来寻址。

除了是通过url创建的,其他使用的方法和普通Actor一样。

实践开始

目录(实践)

使用Java

Scala和Java可以非常方便的互操作,前面已经有大量Scala直接使用Java的例子。

同样Java也可以使用Scala。这个例子演示使用@BeanProperty注解来生成Java Style的Bean。

尝试将

var name: String

修改为

@BeanProperty var name: String

这样就给bean添加了getter/setter。 Apache BeanUtils就可以正常工作。

相等性

在Scala中==等效于equals,这一点和Java不同。更自然一些。

写一个完全正确的equal函数并不容易,这个例子也有子类会不对称的Bug。

尝试将class修改为case class,并删除equals函数。

case类会自动生成正确的equals函数。

抽取器

抽取器可以帮助模式匹配进行解构。

这个例子是构建一个Email抽取器,只要实现unapply函数就可以了。

Scala的正则表达式会自带抽取器,可以抽取出一个List。List里的元素是匹配()里的表达式。

抽取器很有用,短短的例子里就有两处使用抽取器:

case user :: domain :: Nil

解构List

case Email(user, domain)

解构Email。

记忆模式

记忆模式可以解决手动编写存取cache代码的麻烦。

这个例子中,memo可以将一个不含cache函数,包装成一个含有cache功能的。

还是斐波那契的例子,通过cache可以使性能提高。

尝试将

fibonacci_(n - 1) + fibonacci_(n - 2)

修改为

memo(fibonacci_)(n - 1) + memo(fibonacci_)(n - 2)

可以提高更多性能。

隐式转换

implicit可以定义一个转换函数,可以在使用相应类型的时候自动转换。

这个例子可以将String自动转换为Date类型。隐式转换时实现DSL的重要工具。

DSL

DSL是Scala最强大武器,可以使一些描述性代码变得极为简单。

这个例子是使用DSL生成JSON。Scala很多看似是语言级的特性也是用DSL做到的。

自己编写DSL有点复杂,但使用起来非常方便。

测试

Scala可以使用Spec2,ScalaTest来测试, DSL可以使测试更方便。

这个例子是测试一个阶乘函数。使用should/in来建立测试用例。

测试是默认并发执行的。

Simple Build Tool

SBT是Scala的最佳编译工具,在他的帮助下,

你甚至不需要安装除JRE外的任何东西,来开发Scala。

例如你想在自己的机器上执行这个Scala-Tour,可以执行左边的命令

关于

这个指南源自于作者对Scala的热爱和对传播Scala的愿望

其他资料:

comments powered by Disqus

Use a spacebar or arrow keys to navigate

Designed by 颜开