@spiritnotes
2016-03-18T07:25:54.000000Z
字数 11516
阅读 1794
Scala
几乎所有语言元素都是表达式,可以通过val定义一个常量,亦可以通过var定义一个变量
函数是一等公民
可以使用def来定义一个函数。函数体是一个表达式。
使用Block表达式的时候,默认最后一行的返回是返回值,无需显式指定。
函数还可以像值一样,赋值给var或val。因此函数也可以作为参数传给另一个函数
由于函数可以像值一样作为参数传递,所以可以方便的实现借贷模式。
这个例子是从/proc/self/stat文件中读取当前进程的pid。
withScanner封装了try-finally块,所以调用者不用再close。
import scala.reflect.io.File
import java.util.Scanner
def withScanner(f: File, op: Scanner => Unit) = {
val scanner = new Scanner(f.bufferedReader)
try {
op(scanner)
} finally {
scanner.close()
}
}
withScanner(File("/proc/self/stat"),
scanner => println("pid is " + scanner.next()))
参数是按名称传递,参数会等到实际使用的时候才会计算。按名称传递参数可以减少不必要的计算和异常。
def log(msg: => String)
可以用class关键字来定义类。并通过new来创建类。
在定义类时可以定义字段,如firstName,lastName。这样做还可以自动生成构造函数。
可以在类中通过def定义函数。var和val定义字段。
函数名是任何字符如+,-,*,/
使用{ def close(): Unit }作为参数类型。因此任何含有close()的函数的类都可以作为参数。
不必使用继承这种不够灵活的特性。
def withClose(closeAble: { def close(): Unit },
op: { def close(): Unit } => Unit) {
try {
op(closeAble)
} finally {
closeAble.close()
}
}
class Connection {
def close() = println("close Connection")
}
val conn: Connection = new Connection()
withClose(conn, conn =>
println("do something with Connection"))
柯里化(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是简化写法
柯里化可以让我们构造出更像原生语言提供的功能的代码
def withClose(closeAble: { def close(): Unit })
(op: { def close(): Unit } => Unit) {
try {
op(closeAble)
} finally {
closeAble.close()
}
}
class Connection {
def close() = println("close Connection")
}
val conn: Connection = new Connection()
withClose(conn){conn =>
println("do something with Connection")}
def withClose[A <: { def close(): Unit }, B](closeAble: A)
(f: A => B): B =
try {
f(closeAble)
} finally {
closeAble.close()
}
class Connection {
def close() = println("close Connection")
}
val conn: Connection = new Connection()
val msg = withClose(conn) { conn =>
{
println("do something with Connection")
"123456"
}
}
println(msg)
Traits就像是有函数体的Interface。使用with关键字来混入。
这个例子是给java.util.ArrayList添加了foreach的功能。
试着再在with ForEachAble[Int]后面加上with JsonAble给list添加toJson的能力
trait ForEachAble[A] {
def iterator: java.util.Iterator[A]
def foreach(f: A => Unit) = {
val iter = iterator
while (iter.hasNext)
f(iter.next)
}
}
trait JsonAble {
def toJson() =
scala.util.parsing.json.JSONFormat.defaultFormatter(this)
}
val list = new java.util.ArrayList[Int]() with ForEachAble[Int] with JsonAble
list.add(1); list.add(2)
println("For each: "); list.foreach(println(_))
println("Json: " + list.toJson())
模式匹配是类似switch-case特性,但更加灵活;也类似if-else,但更加简约
def fibonacci(in: Any): Int = in match {
case 0 => 0
case 1 => 1
case n:Int if (n>1) => fibonacci(n - 1) + fibonacci(n - 2)
case n:String => fibonacci(n.toInt)
case _ => 0
}
println(fibonacci(3))
println(fibonacci(-3))
println(fibonacci("3"))
case class 顾名思义就是为case语句专门设计的类, 在普通类的基础上添加了和类名一直的工厂方法, 还添加了hashcode,equals和toString等方法。
abstract class Expr
case class FibonacciExpr(n: Int) extends Expr {
require(n >= 0)
}
case class SumExpr(a: Expr, b: Expr) extends Expr
def value(in: Expr): Int = in match {
case FibonacciExpr(0) => 0
case FibonacciExpr(1) => 1
case FibonacciExpr(n) =>
value(SumExpr(FibonacciExpr(n - 1), FibonacciExpr(n - 2)))
case SumExpr(a, b) => value(a) + value(b)
case _ => 0
}
println(value(FibonacciExpr(3)))
这个例子是用指令式编程判断一个List中是否含有奇数
试着将containsOdd(list)替换为list.exists((x: Int) => x % 2 ==1)通过将函数作为参数,可以使程序更简洁。还可以再简化为list.exists(_ % 2 == 1)可以用_替代参数
val list = List(1, 2, 3, 4)
def containsOdd(list: List[Int]): Boolean = {
for (i <- list) {
if (i % 2 == 1)
return true;
}
return false;
}
println("list contains Odd ? " + containsOdd(list))
函数式除了能简化代码外,更重要的是他关注的是Input和Output,函数本身没有副作用。就是Unix pipeline一样,简单的命令组合在一起威力无穷。
val file = List("warn 2013 msg", "warn 2012 msg",
"error 2013 msg", "warn 2013 msg")
println("cat file | grep 'warn' | grep '2013' | wc : "
+ file.filter(_.contains("warn")).filter(_.contains("2013")).size)
val file = List("warn 2013 msg", "warn 2012 msg",
"error 2013 msg", "warn 2013 msg")
def wordcount(str: String): Int = str.split(" ").count("msg" == _)
val num = file.map(wordcount).reduceLeft(_ + _)
println("wordcount:" + num)
尾递归是递归的一种,特点在于会在函数的最末调用自身。尾递归是函数式编程的常见写法。尾递归会在编译期优化,因此不用担心递归造成的栈溢出问题。
def foldLeft(list: List[Int])(init: Int)(f: (Int, Int) => Int): Int = {
list match {
case List() => init
case head :: tail => foldLeft(tail)(f(init, head))(f)
}
}
val num = foldLeft(file.map(wordcount))(0)(_ + _)
循环语句是指令式编程的常见语句,Scala对其加以改进,成为适应函数式风格的利器。
For循环也是有返回值的,返回的是一个List。在每一轮迭代中加入yield,yield后的值可以加入到List中。
val counts =
for (line <- file)
yield wordcount(line)
Scala提供了Option机制来解决,代码中不断检查null的问题。
这个例子包装了getProperty方法,使其返回一个Option。 这样就可以不再漫无目的地null检查。只要Option类型的值即可。
使用pattern match来检查是常见做法。也可以使用getOrElse来提供当为None时的默认值。
给力的是Option还可以看作是最大长度为1的List,List的强大功能都可以使用。再见 NullException
def getProperty(name: String): Option[String] = {
val value = System.getProperty(name)
if (value != null) Some(value) else None
}
val osName = getProperty("os.name")
osName match {
case Some(value) => println(value)
case _ => println("none")
}
println(osName.getOrElse("none"))
osName.foreach(print _)
Lazy可以延迟初始化字段。加上lazy的字段会在第一次访问的时候初始化,而不是类初始化的时候初始化。
Lazy非常适合于初始化非常耗时的场景
class ScalaCurrentVersion(val url: String) {
lazy val source= {
println("fetching from url...")
scala.io.Source.fromURL(url).getLines().toList
}
lazy val majorVersion = source.find(_.contains("version.major"))
lazy val minorVersion = source.find(_.contains("version.minor"))
}
val version = new ScalaCurrentVersion(
"https://raw.github.com/scala/scala/master/build.number")
println("get scala version from " + version.url)
version.majorVersion.foreach(println _)
version.minorVersion.foreach(println _)
Actor是Scala的并发模型。在2.10之后的版本中,使用Akka作为其推荐Actor实现。
Actor是类似线程的实体,有一个邮箱。 Actor可以通过system.actorOf来创建,receive获取邮箱消息,!向邮箱发送消息。
import akka.actor.{ Actor, ActorSystem, Props }
val system = ActorSystem()
class EchoServer extends Actor {
def receive = {
case msg: String => println("echo " + msg)
}
}
val echoServer = system.actorOf(Props[EchoServer])
echoServer ! "hi"
system.shutdown
可以通过更简化的办法声明Actor。
通过akka.actor.ActorDSL中的actor函数。这个函数可以接受一个Actor的构造器Act,启动并返回Actor。
import akka.actor.ActorDSL._
import akka.actor.ActorSystem
implicit val system = ActorSystem()
val echoServer = actor(new Act {
become {
case msg => println("echo " + msg)
}
})
echoServer ! "hi"
system.shutdown
Actor比线程轻量。在Scala中可以创建数以百万级的Actor。奥秘在于Actor直接可以复用线程。
Actor和线程是不同的抽象,他们的对应关系是由Dispatcher决定的。
这个例子创建4个Actor,每次调用的时候打印自身线程名称。
可以发现Actor和线程之间没有一对一的对应关系。一个Actor可以使用多个线程,一个线程也会被多个Actor复用。
import akka.actor.{ Actor, Props, ActorSystem }
import akka.testkit.CallingThreadDispatcher
implicit val system = ActorSystem()
class EchoServer(name: String) extends Actor {
def receive = {
case msg => println("server" + name + " echo " + msg +
" by " + Thread.currentThread().getName())
}
}
val echoServers = (1 to 10).map(x =>
system.actorOf(Props(new EchoServer(x.toString))
.withDispatcher(CallingThreadDispatcher.Id)))
(1 to 10).foreach(msg =>
echoServers(scala.util.Random.nextInt(10)) ! msg.toString)
system.shutdown
Actor非常适合于较耗时的操作。比如获取网络资源。
这个例子通过调用ask函数来获取一个Future。
在Actor内部通过 sender ! 传递结果。
Future像Option一样有很多高阶方法,可以使用foreach查看结果。
import akka.actor.ActorDSL._
import akka.pattern.ask
implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
implicit val system = akka.actor.ActorSystem()
val versionUrl = "https://raw.github.com/scala/scala/master/starr.number"
val fromURL = actor(new Act {
become {
case url: String => sender ! scala.io.Source.fromURL(url)
.getLines().mkString("\n")
}
})
val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000))
version.foreach(println _)
system.shutdown
异步操作可以最大发挥效能。Scala的Futrue很强大,可以异步返回。
可以实现Futrue的onComplete方法。当Futrue结束的时候就会回调。
在调用ask的时候,可以设定超时。
import akka.actor.ActorDSL._
import akka.pattern.ask
implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
implicit val system = akka.actor.ActorSystem()
val versionUrl = "https://raw.github.com/scala/scala/master/starr.number"
val fromURL = actor(new Act {
become {
case url: String => sender ! scala.io.Source.fromURL(url)
.getLines().mkString("\n")
}
})
val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000))
version onComplete {
case msg => println(msg); system.shutdown
}
这个例子是访问若干URL,并记录时间。如果能并行访问,就可以大幅提高性能。
尝试将urls.map修改为urls.par.map这样每个map中的函数都可以并发执行。
当函数式和并发结合,就会这样让人兴奋
val urls = List("http://scala-lang.org",
"https://github.com/yankay/scala-tour")
def fromURL(url: String) = scala.io.Source.fromURL(url)
.getLines().mkString("\n")
val t = System.currentTimeMillis()
urls.map(fromURL(_))
println("time: " + (System.currentTimeMillis - t) + "ms")
这个例子是访问若干URL,并记录时间。
并行集合支持大部分集合的功能。
在前面有一个wordcount例子,也可以用并行集合加以实现。
不增加程序复杂性,却能大幅提高利用多核的能力。
val file = List("warn 2013 msg", "warn 2012 msg",
"error 2013 msg", "warn 2013 msg")
def wordcount(str: String): Int = str.split(" ").count("msg" == _)
val num = file.par.map(wordcount).par.reduceLeft(_ + _)
println("wordcount:" + num)
Actor是并发模型,也使用于分布式。
这个例子创建一个Echo服务器,通过actorOf来注册自己。
然后再创建一个client,通过akka url来寻址。
除了是通过url创建的,其他使用的方法和普通Actor一样。
import akka.actor.{ Actor, ActorSystem, Props }
import com.typesafe.config.ConfigFactory
implicit val system = akka.actor.ActorSystem("RemoteSystem",
ConfigFactory.load.getConfig("remote"))
class EchoServer extends Actor {
def receive = {
case msg: String => println("echo " + msg)
}
}
val server = system.actorOf(Props[EchoServer], name = "echoServer")
val echoClient = system
.actorFor("akka://RemoteSystem@127.0.0.1:2552/user/echoServer")
echoClient ! "Hi Remote"
system.shutdown
import org.apache.commons.beanutils.BeanUtils
import scala.beans.BeanProperty
class SimpleBean(var name: String) {
}
@BeanProperty val bean = new SimpleBean("foo")
println(BeanUtils.describe(bean))
在Scala中==等效于equals,这一点和Java不同。更自然一些。
写一个完全正确的equal函数并不容易,这个例子也有子类会不对称的Bug。
尝试将class修改为case class,并删除equals函数。
case类会自动生成正确的equals函数。
class Person(val name: String) {
override def equals(other: Any) = other match {
case that: Person => name.equals(that.name)
case _ => false
}
}
println(new Person("Black") == new Person("Black"))
抽取器可以帮助模式匹配进行解构。
这个例子是构建一个Email抽取器,只要实现unapply函数就可以了。
Scala的正则表达式会自带抽取器,可以抽取出一个List。List里的元素是匹配()里的表达式。
抽取器很有用,短短的例子里就有两处使用抽取器:
case user :: domain :: Nil解构List
case Email(user, domain)解构Email。
import scala.util.matching.Regex
object Email {
def unapply(str: String) = new Regex("""(.*)@(.*)""")
.unapplySeq(str).get match {
case user :: domain :: Nil => Some(user, domain)
case _ => None
}
}
"user@domain.com" match {
case Email(user, domain) => println(user + "@" + domain)
}
记忆模式可以解决手动编写存取cache代码的麻烦。
这个例子中,memo可以将一个不含cache函数,包装成一个含有cache功能的。
还是斐波那契的例子,通过cache可以使性能提高。
尝试将fibonacci_(n - 1) + fibonacci_(n - 2)修改为memo(fibonacci_)(n - 1) + memo(fibonacci_)(n - 2)可以提高更多性能。
import scala.collection.mutable.WeakHashMap
val cache = new WeakHashMap[Int, Int]
def memo(f: Int => Int) = (x: Int) => cache.getOrElseUpdate(x, f(x))
def fibonacci_(in: Int): Int = in match {
case 0 => 0;
case 1 => 1;
case n: Int => fibonacci_(n - 1) + fibonacci_(n - 2)
}
val fibonacci: Int => Int = memo(fibonacci_)
val t1 = System.currentTimeMillis()
println(fibonacci(40))
println("it takes " + (System.currentTimeMillis() - t1) + "ms")
val t2 = System.currentTimeMillis()
println(fibonacci(40))
println("it takes " + (System.currentTimeMillis() - t2) + "ms")
implicit可以定义一个转换函数,可以在使用相应类型的时候自动转换。
这个例子可以将String自动转换为Date类型。隐式转换时实现DSL的重要工具。
import java.text.SimpleDateFormat
implicit def strToDate(str: String) =
new SimpleDateFormat("yyyy-MM-dd").parse(str)
println("2013-01-01 unix time: " + "2013-01-01".getTime()/1000l)
DSL是Scala最强大武器,可以使一些描述性代码变得极为简单。
这个例子是使用DSL生成JSON。Scala很多看似是语言级的特性也是用DSL做到的。
自己编写DSL有点复杂,但使用起来非常方便。
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
import java.util.Date
case class Twitter(id: Long, text: String, publishedAt: Option[java.util.Date])
var twitters = Twitter(1, "hello scala", Some(new Date())) ::
Twitter(2, "I like scala tour", None) :: Nil
var json = ("twitters"
-> twitters.map(
t => ("id" -> t.id)
~ ("text" -> t.text)
~ ("published_at" -> t.publishedAt.toString())))
println(pretty(render(json)))
Scala可以使用Spec2,ScalaTest来测试, DSL可以使测试更方便。
这个例子是测试一个阶乘函数。使用should/in来建立测试用例。
测试是默认并发执行的。
import org.specs2.mutable._
class FactorialSpec extends Specification {
args.report(color = false)
def factorial(n: Int) = (1 to n).reduce(_ - _)
"The 'Hello world' string" should {
"factorial 3 must be 6" in {
factorial(3) mustEqual 6
}
"factorial 4 must greaterThan 6" in {
factorial(4) must greaterThan(6)
}
}
}
specs2.run(new FactorialSpec)
SBT是Scala的最佳编译工具,在他的帮助下,
你甚至不需要安装除JRE外的任何东西,来开发Scala。