@spiritnotes
2016-03-18T07:25:54.000000Z
字数 11516
阅读 2040
Scala
几乎所有语言元素都是表达式,可以通过val定义一个常量,亦可以通过var定义一个变量
函数是一等公民
可以使用def来定义一个函数。函数体是一个表达式。
使用Block表达式的时候,默认最后一行的返回是返回值,无需显式指定。
函数还可以像值一样,赋值给var或val。因此函数也可以作为参数传给另一个函数
由于函数可以像值一样作为参数传递,所以可以方便的实现借贷模式。
这个例子是从/proc/self/stat文件中读取当前进程的pid。
withScanner封装了try-finally块,所以调用者不用再close。
import scala.reflect.io.Fileimport java.util.Scannerdef 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 = iteratorwhile (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 JsonAblelist.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 => 0case 1 => 1case 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 Exprcase class FibonacciExpr(n: Int) extends Expr {require(n >= 0)}case class SumExpr(a: Expr, b: Expr) extends Exprdef value(in: Expr): Int = in match {case FibonacciExpr(0) => 0case FibonacciExpr(1) => 1case 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() => initcase 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.ActorSystemimplicit 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.CallingThreadDispatcherimplicit 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.askimplicit val ec = scala.concurrent.ExecutionContext.Implicits.globalimplicit 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.askimplicit val ec = scala.concurrent.ExecutionContext.Implicits.globalimplicit 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.ConfigFactoryimplicit 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.BeanUtilsimport scala.beans.BeanPropertyclass 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.Regexobject 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.WeakHashMapval 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.SimpleDateFormatimplicit 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.Datecase 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) :: Nilvar 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。