高效的 Scala HENRIK ENGSTRÖM TYPESAFE 软件工程师 @h3nk3 自我介绍 ✦ 从 1998 年开始做咨询 — 主要针对 Java ✦ 从 2010 年开始进行 Scala 编程 ✦ 从 2011 年开始为 Typesafe 公司工作 ✦ Akka 团队荣誉会员 ✦ Typesafe 控制台技术负责人 ✦ 阿森纳球迷 + 武术家 关于 Typesafe ✦ Typesafe 平台 ‣ Play、Akka、Scala、Scala IDE、 Slick、SBT 等 ✦ 订阅 ✦ 培训和咨询 议题 ✦ 基础知识 ✦ Scala 中的对象定位 ✦ 隐式 ✦ 类型 ✦ 集合 ✦ 模式匹配 ✦ 函数式编程 基础知识 使用 REPL // REPL = Read Eval Print Loop $ scala_home/bin/scala Welcome to Scala version 2.10.0 scala> println("Hello, world!") Hello, world! scala> REPL 和 JAR // Put JAR files in scala_home/lib to get access $ scala_home/bin/scala Welcome to Scala version 2.10.0 scala> import com.x.y.z.MyClass scala> val instance = new MyClass scala> instance.myMethod REPL 2013 ✦ IDE 工作表 - Scala IDE: 棒极了 - IntelliJ: 很好 表达式与语句 // JAVA String result = null; if (z < 9) result = "<9" else result = ">=9"; System.out.println("Result: " + result); // SCALA println("Result: " + if (z < 9) "<9" else ">=9")) 语句突击测试 (Pop Quiz) // What type is variable quiz? var x = 1 val quiz = while (x < 10) { println("X is: " + x) x += 1 } ✦ Scala 中的所有表达式返回一个类型 请注意无钓鱼说明! // Don’t tell the computer how to fish import scala.collection.mutable.{HashSet => MHS} def findPeopleInCity(c: String, ppl: Seq[People]): Set[People] = { val found = new MHS[People]() for (p <- ppl) for (a <- p.address) if (a.city == c) found.put(p) found } 而只是点鱼 def findPeopleInCity(c: String, ppl: Seq[People]): Set[People] = { for { p <- ppl.toSet[People] a <- p.address if a.city == c } yield p } // SQL LIKE SYNTAX; FROM, WHERE, AND, SELECT 保持不可变 // 1) Mutable code leads to cloning // 2) Cloning leads to performance degradation // => Mutable code leads to worse performance class Footballer { private var cars = Array[Car]() def setCars(c: Array[Car]): Unit = cars = c.clone def getCars: Array[Car] = cars.clone } 保持不可变 // Safer code - use immutable collection class Footballer { private var cars = Vector.empty[Car] def setCars(c: Vector[Car]) = cars = c def getCars: Vector[Car] = cars } Case 类 ftw // Case classes make the class immutable scala> case class Car(brand: String) scala> case class Footballer(name: String, team: String, cars: Vector[Car] = Vector.empty) scala> var jw = new Footballer("Jack Wilshire", "Arsenal") Footballer(Jack Wilshire, Arsenal, Vector()) scala> jw = jw.copy(cars = Vector(Car("Porsche"))) Footballer(Jack Wilshire, Arsenal, Vector(Car(Porsche))) 不可变的优势 ✦ 简单均等 ✦ 简单散列代码 ✦ 无需锁定 ✦ 无防御性复制 ✦ Scala Case 类 ‣ 自动均等 + 散列代码 (Murmur) ‣ 还有很多其他好处(例如复制) 局部可变性 // Sometimes local mutability makes sense import scala.collection.mutable.ArrayBuffer class MyClass { def theData: Seq[Int] = { val buffer = new ArrayBuffer[Int]() populateData(buffer) buffer.toSeq } } 使用选项 // A classic game of null def auth(usr: String, pwd: String): Privileges = if (usr == null || usr.isEmpty || pwd == null || pwd.isEmpty || !validCredentials(usr, pwd)) withPrivileges(Anonymous) else privilegesFor(usr) 使用选项 def auth(usr: Option[String], pwd: Option[String]): Privileges = { val privileges: Option[Privileges] = { u <- usr p <- pwd if (!u.isEmpty && !p.isEmpty) if canAuthenticate(u, p) } yield privilegesFor(u) privileges getOrElse withPrivileges(Anonymous) } 对象定位 抽象成员的 val trait SquareShape { val width: Int val height: Int val area: Int = width * height } class Rect(w: Int, h: Int) extends SquaredShape { override val width = w override val height = h } scala> val r1 = new Rectangle(1, 314) scala> r1.height res0: Int = 314 scala> r1.area res1: Int = 0 Scala 初始化顺序 ✦ 摘自 Scala 规范(第 5.1 节) - http://www.scalalang.org/docu/files/ScalaReference. pdf ‣ 首先评估超类构建函数 ‣ 然后是模板线性化中的所有基类…… ‣ 最后评估语句序列状态 def is much better trait SquareShape { def width: Int def height: Int def area: Int = width * height } class Rect(w: Int, h: Int) extends SquaredShape { override val width = w override val height = h } // or even better case class Rect(width: Int, height: Int) extends SquaredShape 为您的 API 加批注 // Non-trivial return types should always be // annotated! def convert(x: Int) = x match { case 1 => 1.toChar case 2 => true case z => z.toByte } def convert(x: Int): AnyVal = x match { 组合与继承 ✦ 与继承相比,更偏好组合 - 易于修改(例如 DI) ✦ 组合可在 Scala 中使用继承 - 引向著名的“蛋糕”模式 让我们来烘培一块“蛋糕” trait UserRepoComponent { def userLocator: UserLocator def userUpdater: UserUpdater trait UserLocator { def findAll: Vector[User] } trait UserUpdater { def save(user: User) } } 正在烘培 trait JPAUserRepoComponent extends UserRepoComponent { def em: EntityManager def userLocator = new JPAUserLocator(em) def userUpdater = new JPAUserUpdater(em) class JPAUserLocator(em: EntityManager) extends UserLocator { def findAll: Vector[User] = em.createQuery("from User", classOf[User]).getResultList.toVector } class JPAUserUpdater(em: EntityManager) extends UserUpdater { def save(user: User) = em.persist(user) } } 服务层 trait UserServiceComponent { def userService: UserService trait UserService { def findAll: Vector[User] def save(user: User): Unit def checkStatusOf(user: User): String } } 服务层实现 trait DefaultUserServiceComponent extends UserServiceComponent { this: UserRepositoryComponent => def userService = new DefaultUserService class DefaultUserService extends UserService { def findAll = userLocator.findAll def save(user: User) = userUpdater.save(user) def checkStatus(user: User) = s"User $user seems okay to me" } } 投入使用 object MyApplication extends Application { val compService = new DefaultUserServiceComponent with JPAUserRepositoryComponent { def em = Persistence.createEntityManagerFactory( "cakepattern").createEntityManager() } val service = compService.userService // use the service } 进行测试 class MyTest extends WordSpec with MustMatchers with Mockito { trait MockedEntityManager { def em = mock[EntityManager] } "service" must { "return all users" in { val compService = new DefaultUserServiceComponent with JPAUserRepositoryComponent with MockedEntityManager // perform tests } } 隐式 有何作用? ✦ 在特定上下文中移除样板代码 ‣ 编译时安全 ‣ 但是必须明确 示例 trait AutoRepository { def find(regId: String)(implicit dbId: DBId): Option[Car] def findAll(country: String)(implicit dbId: DBId): Seq[Car] } class DefaultAutoRepository extends AutoRepository { def find(regId: String)(implicit dbId: DBId): Option[Car] = { // ... } def findAll(country: String)(implicit dbId: DBId): Seq[Car] = { // ... } } 示例(续) // Anti pattern class CarFinder { val dbId = DbId("Dealer1") val repo = new DefaultAutoRepository def getCar(regId: String): Option[Car] = repo.find(regId)(dbId) def listCars(country: String): Seq[Car] = repo.findAll(country)(dbId) } 示例(续) // Use implicits => much cleaner code class CarFinder { implicit val dbId = DbId("Dealer1") val repo = new DefaultAutoRepository def getCar(regId: String): Option[Car] = repo.find(regId) def listCars(country: String): Seq[Car] = repo.findAll(country) } 编译器练习 ✦ 隐式作用域 ‣ 词法: 当前作用域、显式导入、通配符 导入 ‣ 部分类型的伴生对象: 类型的伴生对象 、参数类型的伴生对象、嵌套类型的外 部对象 ✦ 编译时可能会消耗大量资源,应谨慎使用 隐式值 trait Logger { def log(msg: String) } object Logger { implicit object DefaultLogger extends Logger { def log(msg: String) = println("DL> " + msg) } def log(msg: String)(implicit logger: Logger) = { logger.log(msg) } } 隐式值 scala> Logger.log("a small test") DL> a small test scala> class MyLogger extends Logger { def log(msg: String) = println("ML:>> " + msg) } scala> implicit def myLogger = new MyLogger scala> Logger.log("another test") ML:>> another test 隐式智慧? deech @deech 调试 #scala 隐式内容如同 在一个拥挤的房间中寻找 farter 类型特性 即,类型类 // "describes generic interfaces using type // parameters such that the implementations can // be created for any type" trait Encodable[T] { def from(t: T): String def to(s: String): T } object Encodable { implicit object IntEncoder extends Encodable[Int] { def from(i: Int): String = "int" + i def to(s: String): Int = s.substring(s.indexOf("int")+3, s.length).toInt } } 使用示例 class MyHandler { def convert[T](t: T)(implicit enc: Encodable[T]): String = enc.from(t) def convert[T](s: String)(implicit enc: Encodable[T]): T = enc.to(s) } scala> val myHandler = new MyHandler scala> myHandler.convert(12345) res0: String = int12345 scala> myHandler.convert(res0) res1: Int = 12345 示例(续) scala> myHandler.convert(12345L) <console>:15: error: could not find implicit value for parameter encoder: Encodable[Long] scala> implicit object LongEnc extends Encodable[Long] { def from(l: Long): String = "long" + l def to(s: String): Long = s.substring(s.indexOf("long")+4, s.length).toLong } scala> myHandler.convert(12345L) 集合 集合概述 不可变集合 可变集合 了解和学习 API // It is absolutely *awesome* scala> val seq = Seq() scala> seq. ++ ++: +: collect collectFirst drop dropRight foreach genericBuilder intersect isDefinedAt lengthCompare lift permutations prefixLength reverseMap runWith sorted toIndexedSeq unzip3 span toIterable updated /: /:\ combinations dropWhile companion max product splitAt toIterator view maxBy scan startsWith toList withFilter filterNot head min reduceLeft stringPrefix toMap zip minBy scanRight sum mkString toSeq zipAll toSet zipWithIndex lastIndexOfSlice reduceRight segmentLength tails indexOfSlice nonEmpty reduceOption tail flatten indexOf lastIndexOf apply copyToBuffer flatMap headOption last andThen copyToArray find reduceLeftOption scanLeft aggregate containsSlice isTraversableAgain iterator reduce sameElements filter hasDefiniteSize isInstanceOf addString contains exists grouped isEmpty :\ compose endsWith groupBy map :+ seq take toStream corresponds fold slice takeWhile toTraversable partition reverse sliding to toVector inits length par repr forall init lastOption padTo distinct foldRight indices lastIndexWhere canEqual diff foldLeft reduceRightOption takeRight asInstanceOf count indexWhere orElse size toString applyOrElse sortBy toArray transpose patch reverseIterator sortWith toBuffer union unzip 给 Java 开发人员的信息 ✦ 使用 Vector 而不是 List ‣ 它更快 ‣ 它对内存的使用更高效 模式匹配 FP 模式匹配 @scala.annotation.tailrec def len[A](v: Vector[A], l: Int): Int = v match { case h :: t => len(t, l + 1) case Nil => l } scala> len(Vector("a","b","c","d") res0: Int = 4 提取并进行“instanceof”判断 def convertedAge(a: Animal): Int = a match { case Dog(name, age) => age * 7 case Human(_, age, _) => age case Walrus("Donny", age) => age / 10 case Walrus(name, age) if name == "Walter" => age case _ => 0 } 相关成本 ✦ 请注意,模式匹配实际上是 if - else if。 ✦ 尝试在开始时放入最常见的发现,以减 少跳跃次数 函数式编程 忠告 ✦ 了解函数式编程的模式 ✦ 阅读有关 Functor、Applicative 和 Monad 的精彩文章 ‣ http://adit.io/posts/2013-04-17functors,_applicatives,_and_monads_in_pictures.html ✦ 查看 scalaz 了解 Scala 中 FP 的更多信 息 值和上下文 23 值 (+8) 函数 31 值 选项 23 23 Some[Int] 无 值和上下文 上下文 Functor 23 (+8) 23 (+8) 不适用! 31 23 31 Some[Int] Some[Int] 无 X (+8) X 无 Functor 定义 trait Functor[F[_]] { def fmap[A,B](fa: F[A], f: A => B): F[B] } Scala 中的 Functor def dblFunc(values: Vector[Int]): Vector[Int] = values map { _ * 2 } scala> dblFunc(Vector(1,2,3,4)) res0: Vector(2,4,6,8) Applicative 23 (+8) 23 (+8) Some[Int] Some[f(x)] 31 Some[Int] Scala Applicative 示例 scala> Vector(1,2,3) res0: Vector[Int] = Vector(1,2,3) scala> List("A","B","C") res1: List[String] = List(A,B,C) scala> res0 zip res1 res2: List[(Int,String)] = List((1,A),(2,B),(3,C)) Some[Int] Monad Some[Int] 24 24 23 isEven 23 24 isEven 24 无 *2 48 Monad 定义 import scala.language.higherKinds trait Monad[M[_]] { def pure[A](a: A): M[A] def bind[A,B](ma: M[A])(f: A => M[B]): M[B] } Scala Monad 示例 def even(number: Option[Int]): Option[Int] = for { n <- number if n % 2 == 0 } yield n scala> even(Some(22)) res1: Option[Int] = Some(22) scala> even(Some(21)) res2: Option[Int] = None 总结 ✦ FUNCTOR - 将函数应用到包装值 ✦ APPLICATIVE - 将包装函数应用到包装值 ✦ MONAD - 将返回包装值的函数应用到包装值 推荐的 Scala 书籍 EOF @h3nk3