面向值(value-oriented )編程有很多優(yōu)勢(shì),特別是用在與函數(shù)式編程結(jié)構(gòu)相結(jié)合。這種風(fēng)格強(qiáng)調(diào)值的轉(zhuǎn)換(譯注:由一個(gè)不變的值生成另一個(gè)不變的值)而非狀態(tài)的改變,生成的代碼是指稱透明的(referentially transparent),提供了更強(qiáng)的不變型(invariants),因此容易實(shí)現(xiàn)。Case類(也被翻譯為樣本類),模式匹配,解構(gòu)綁定(destructuring bindings),類型推斷,輕量級(jí)的閉包和方法創(chuàng)建語(yǔ)法都是這一類的工具。
Case類可實(shí)現(xiàn)代數(shù)數(shù)據(jù)類型(ADT)編碼:它們對(duì)大量的數(shù)據(jù)結(jié)構(gòu)進(jìn)行建模時(shí)有用,用強(qiáng)不變類型(invariants)提供了簡(jiǎn)潔的代碼。尤其在結(jié)合模式匹配情況下。模式匹配實(shí)現(xiàn)了全面解析提供更強(qiáng)大的靜態(tài)保護(hù)。 (譯注:ADTs是Algebraic Data Type代數(shù)數(shù)據(jù)類型的縮寫(xiě),關(guān)于這個(gè)概念見(jiàn)我的另一篇博客)
下面是用case類模擬代數(shù)數(shù)據(jù)類型的模式
sealed trait Tree[T]
case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T]
case class Leaf[T](value: T) extends Tree[T]
類型 Tree[T] 有兩個(gè)構(gòu)造函器:Node和Leaf。定義類型為sealed(封閉類)允許編譯器進(jìn)行徹底的分析(這是針對(duì)模式匹配的,參考Programming in Scala)因?yàn)闃?gòu)造器將不能從外部源文件中添加。
與模式匹配一同,這個(gè)建模使得代碼簡(jiǎn)潔并且顯然是正確的(obviously correct)
def findMin[T <: Ordered[T]](tree: Tree[T]) = tree match {
case Node(left, right) => Seq(findMin(left), findMin(right)).min
case Leaf(value) => value
}
盡管一些遞歸結(jié)構(gòu),如樹(shù)的組成是典型的ADTs(代數(shù)數(shù)據(jù)類型)應(yīng)用,它們的用處領(lǐng)域更大。 disjoint,unions特別容易的用ADTs建模;這些頻繁發(fā)生在狀態(tài)機(jī)上(state machines)。
Option類型是一個(gè)容器,空(None)或滿(Some(value))二選一。它提供了使用null的另一種安全選擇,應(yīng)該盡可能的替代null。它是一個(gè)集合(最多只有一個(gè)元素)并用集合操所修飾,盡量用Option。
用
var username: Option[String] = None
...
username = Some("foobar")
代替
var username: String = null
...
username = "foobar"
因?yàn)榍罢吒踩篛ption類型靜態(tài)地強(qiáng)制username必須對(duì)空(emptyness)做檢測(cè)。
對(duì)一個(gè)Option值做條件判斷應(yīng)該用foreach
if (opt.isDefined)
operate(opt.get)
上面的代碼應(yīng)該用下面的方式替代:
opt foreach { value =>
operate(value)}
風(fēng)格可能看起來(lái)有些古怪,但更安全,更簡(jiǎn)潔。如果兩種情況都有(Option的None或Some),用模式匹配
opt match {
case Some(value) => operate(value)
case None => defaultAction()
}
但如果缺少的是缺省值,用getOrElse方法:
operate(opt getOrElse defaultValue)
不要過(guò)度使用Option: 如果有一個(gè)明確的缺省值——一個(gè)Null對(duì)象——直接用Null而不必用Option
Option還有一個(gè)方便的構(gòu)造器用于包裝空值(nullable value)
Option(getClass.getResourceAsStream("foo"))
得到一個(gè) Option[InputStream] 假定空值(None)時(shí)getResourceAsStream會(huì)返回null。
模式匹配(x match { …) 在良好的Scala代碼中無(wú)處不在:用于合并條件執(zhí)行、解構(gòu)(destructuring) 、在構(gòu)造中造型。使用好模式匹配可以增加程序的明晰度和安全性。
使用模式匹配實(shí)現(xiàn)類型轉(zhuǎn)換:
obj match {
case str: String => ...
case addr: SocketAddress => ...
模式匹配在和解構(gòu)(destructuring)聯(lián)合使用時(shí)效果最好(例如你要匹配case類);下面的寫(xiě)法
animal match {
case dog: Dog => "dog (%s)".format(dog.breed)
case _ => animal.species
}
應(yīng)該被替代為:
animal match {
case Dog(breed) => "dog (%s)".format(breed)
case other => other.species
}
寫(xiě)自定義的抽取器?(extractor)時(shí)必須有雙重構(gòu)造器(譯注:成對(duì)出現(xiàn)的apply方法與unapply方法),否則可能是不適合的。
當(dāng)默認(rèn)的方法更有意義時(shí),對(duì)條件執(zhí)行不要用模式匹配。集合庫(kù)的方法通常返回Options,避免:
val x = list match {
case head :: _ => head
case Nil => default
}
因?yàn)?/p>
val x = list.headOption getOrElse default
更短并且更能表達(dá)目的。
Scala提供了定義PartialFunction的語(yǔ)法簡(jiǎn)寫(xiě):
val pf: PartialFunction[Int, String] = {
case i if i%2 == 0 => "even"
}
它們也可能和 orElse 組合:
val tf: (Int => String) = pf orElse { case _ => "odd"}
tf(1) == "odd"
tf(2) == "even"
偏函數(shù)出現(xiàn)在很多場(chǎng)景,并以PartialFunction有效地編碼 ,例如 方法參數(shù):
trait Publisher[T] {
def subscribe(f: PartialFunction[T, Unit])
}
val publisher: Publisher[Int] = ..
publisher.subscribe {
case i if isPrime(i) => println("found prime", i)
case i if i%2 == 0 => count += 2
/* ignore the rest */
}
或在返回一個(gè)Option的情況下:
// Attempt to classify the the throwable for logging.
type Classifier = Throwable => Option[java.util.logging.Level]
可以更好的用PartialFunction表達(dá)
type Classifier = PartialFunction[Throwable, java.util.Logging.Level]
因?yàn)樗峁┝烁玫目山M合性:
val classifier1: Classifier
val classifier2: Classifier
val classifier = classifier1 orElse classifier2 orElse { _ => java.util.Logging.Level.FINEST }
解構(gòu)綁定與模式匹配有關(guān)。它們用了相同的機(jī)制,但解構(gòu)綁定可應(yīng)用在當(dāng)匹配只有一種選項(xiàng)的時(shí)候 (以免你接受異常的可能)。解構(gòu)綁定特別適用于元組(tuple)和樣本類(case class).
val tuple = ('a', 1)
val (char, digit) = tuple
val tweet = Tweet("just tweeting", Time.now)
val Tweet(text, timestamp) = tweet
當(dāng)使用lazy修飾一個(gè)val成員時(shí),其賦值情況是在需要時(shí)才賦值的(by need),因?yàn)镾cala中成員與方法是等價(jià)的(除了private[this]成員)
lazy val field = computation()
相當(dāng)于下面的簡(jiǎn)寫(xiě):
var _theField = None
def field = if (_theField.isDefined) _theField.get else {
_theField = Some(computation())
_theField.get
}
也就是說(shuō),它在需要時(shí)計(jì)算結(jié)果并會(huì)記住結(jié)果,在要達(dá)到這種目的時(shí)使用lazy成員;但當(dāng)語(yǔ)意上需要惰性賦值時(shí)(by semantics),要避免使用惰性賦值,這種情況下,最好顯式賦值因?yàn)樗沟贸杀灸P褪敲鞔_的,并且副作用被嚴(yán)格的控制。
Lazy成員是線程安全的。
方法參數(shù)可以指定為傳名參數(shù) (by-name) 意味著參數(shù)不是綁定到一個(gè)值,而是一個(gè)可能需要反復(fù)進(jìn)行的計(jì)算。這一特性需要小心使用; 期待傳值(by-value)語(yǔ)法的調(diào)用者會(huì)感到驚訝。這一特性的動(dòng)機(jī)是構(gòu)造語(yǔ)法自然的 DSLs——使新的控制結(jié)構(gòu)可以看起來(lái)更像本地語(yǔ)言特征。
只在下面的控制結(jié)構(gòu)中使用傳名調(diào)用, 調(diào)用者明顯傳遞的是一段代碼塊(block)而非一個(gè)確定的計(jì)算結(jié)果。傳名參數(shù)必須放在參數(shù)列表的最后一位。當(dāng)使用傳名調(diào)用時(shí),確保方法名稱讓調(diào)用者明顯感知到方法參數(shù)是傳名參數(shù)。
當(dāng)你想要一個(gè)值被計(jì)算多次,特別是這個(gè)計(jì)算會(huì)引起副作用時(shí),使用顯式函數(shù):
class SSLConnector(mkEngine: () => SSLEngine)
這樣意圖很明確,調(diào)用者不會(huì)感到驚奇。
flatMap
flatMap——結(jié)合了map 和 flatten —— 的使用要特別小心,它有著難以琢磨的威力和強(qiáng)大的實(shí)用性。類似它的兄弟 map,它也是經(jīng)常在非傳統(tǒng)的集合中使用的,例如 Future , Option。它的行為由它的(函數(shù))簽名揭示;對(duì)于一些容器 Container[A]
flatMap[B](f: A => Container[B]): Container[B]
flatMap對(duì)集合中的每個(gè)元素調(diào)用了 函數(shù) f 產(chǎn)生一個(gè)新的集合,將它們?nèi)?flatten 后放入結(jié)果中。例如,獲取兩個(gè)字符的字符串的所有排列,相同的字符不能出現(xiàn)兩次
val chars = 'a' to 'z'
val perms = chars flatMap { a =>
chars flatMap { b =>
if (a != b) Seq("%c%c".format(a, b))
else Seq()
}
}
等價(jià)于下面這段更簡(jiǎn)潔的 for-comprehension (基本就是針對(duì)上面的語(yǔ)法糖)
val perms = for {
a <- chars
b <- chars
if a != b
} yield "%c%c".format(a, b)
flatMap
在處理Options常常很有用—— 它將多個(gè)options鏈合并為一個(gè),
val host: Option[String] = ..
val port: Option[Int] = ..
val addr: Option[InetSocketAddress] =
host flatMap { h =>
port map { p =>
new InetSocketAddress(h, p)
}
}
也可以使用更簡(jiǎn)潔的for來(lái)實(shí)現(xiàn):
val addr: Option[InetSocketAddress] = for {
h <- host
p <- port
} yield new InetSocketAddress(h, p)
對(duì)flatMap在在Futures中的使用futures一節(jié)中有討論。
更多建議: