def - Active Error

advertisement
高效的 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
Download