Clojure首先是FP, 可是因爲基於JVM, 因此不得已須要作出一些妥協, 包含一些OO的編程方式
Scala首先是OO, Java語法過於冗餘, 一種比較平庸的語言, Scala首先作的是簡化, 以更爲簡潔的方式來編寫OO, 主要利用‘type inference’能推斷出來的, 你就不用寫, 但若是僅僅這樣, 不如用python
因此Scala象其名字同樣, 「可伸展的語言」, 它是個大的集市, 它積極吸納其餘語言的優秀的特徵, 最重要的就是FP, 你可使用Scala來寫OO, 但它推薦使用FP的方式來寫Scala; 還包括Erlang裏面的actor模型
因此Scala並不容易學, 由於比較繁雜html
我主要對Scala programing作個筆記, 下面這個哥們整理的更好前端
http://qiujj.com/static/Scala-Handbook.htmjava
http://docs.scala-lang.org/ , Scala官方文檔python
http://www.scala-lang.org/api/current/#package, 參考手冊react
http://twitter.github.io/effectivescala/index-cn.html, Effective Scalagit
scala> 1 + 2
res0: Int = 3
scala> res0 * 3
res1: Int = 9
scala> println("Hello, world!")
Hello, world!
scala中;經常能夠省略, 但若是一行有多條語句, 則必須加上程序員
val s = "hello"; println(s)
若是一條語句, 要用多行寫es6
x
+ y
這樣會看成2個語句, 兩種方法解決, github
(x
+ y) //加括號 面試x +
y +
z //把操做符寫在前一行, 暗示這句沒完
Rich wrappers, 爲基本類型提供更多的操做
val, 不可變變量, 常量, 適用於FP
var, 可變變量, 適用於OO
scala> val msg = "Hello, world!" msg: java.lang.String = Hello, world! scala> msg = "Goodbye cruel world!" <console>:5: error: reassignment to val msg = "Goodbye cruel world!"
能夠簡寫爲, 返回值類型不須要寫, 能夠推斷出, 只有一條語句, 因此{}能夠省略
scala> def max2(x: Int, y: Int) = if (x > y) x else y max2: (Int,Int)Int
簡單的funciton, 返回值爲Unit, 相似Void(區別在於void爲無返回值, 而scala都有返回值, 只是返回的爲Unit, ())
scala> def greet() = println("Hello, world!")
greet: ()Unit
scala> greet() == ()
Boolean = true
函數參數不可變
def add(b: Byte): Unit = {
b = 1 // This won’t compile, because b is a val
sum += b
}
重複參數, 最後的*表示可變參數列表, 可傳入多個string
scala> def echo(args: String*) = for (arg <- args) println(arg)
scala> echo("hello", "world!")
hello
world!
如何翻譯...
Scala FP的基礎, function做爲first class, 以function literal的形式做爲參數被傳遞
args.foreach((arg: String) => println(arg))
args.foreach(arg => println(arg)) //省略類型
args.foreach(println) //其實連參賽列表也能夠省略
能夠看到scala在省略代碼量上能夠說下足功夫, 只要能推斷出來的你均可以不寫, 這也是對於靜態類型系統的一種形式的彌補
對於oo程序員, 可能比較難理解, 其實等於
for (arg <args)
println(arg)
因爲scala是偏向於FP的, 因此全部控制結構都有返回值, 這樣便於FP編程
If, 能夠返回值
val filename = if (!args.isEmpty) args(0) else "default.txt"
While, 在FP裏面不推薦使用循環, 應該用遞歸,儘可能避免
For, 沒有python和clojure的好用或簡潔
for ( file <- filesHere //generator,用於遍歷,每次file都會被重新初始化 if file.isFile; //過濾條件, 多個間須要用; if file.getName.endsWith(".scala"); //第二個過濾 line <- fileLines(file) //嵌套for trimmed = line.trim //Mid-stream variable bindings, val類型,相似clojure let if trimmed.matches(pattern) ) println(file +": "+ trimmed)
//for默認不會產生新的集合, 必須使用yield def scalaFiles = for { file <- filesHere if file.getName.endsWith(".scala") } yield file //yield產生新的集合,相似python
match, switch-case
能夠返回值, FP風格, 這樣只須要最後println一次
默認會break, 不須要每次本身加
val firstArg = if (!args.isEmpty) args(0) else "" val friend = firstArg match { case "salt" => "pepper" case "chips" => "salsa" case "eggs" => "bacon" case _ => "huh?" //default } println(friend)
參考, http://www.ibm.com/developerworks/cn/java/j-ft13/index.html
Option和Either都是用來讓返回值能夠有兩個選擇
而Option是比較簡單的版本, 兩個選擇, 必定是成功Some, 和失敗None
Option意味着可能有值some(x), 也可能沒有值(用None對象, 表示缺失), 典型的例子就是從字典裏取值
val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo") def show(x: Option[String]) = x match { //Option類型, 可選的String case Some(s) => s case None => "?" } scala> show(capitals get "France") res24: String = Paris scala> show(capitals get "North Pole") res25: String = ?
之前的方式, 好比Java, 經過null來表示沒有取到值, 可是有的時候null可能做爲合法值出現, 就須要特殊處理, 很麻煩
而Scala提供option來比較優雅的解決這個問題
Either, 更爲通用一些, 可用本身定義兩種選擇, 直接看個spark源碼中的例子,
對於PutResult中的data, 有多是ByteBuffer或者Iterator
而使用的時候, 使用Left和Right來選擇到底用哪個
private[spark] case class PutResult(size: Long, data: Either[Iterator[_], ByteBuffer])
PutResult(sizeEstimate, Left(values.iterator)) PutResult(bytes.limit(), Right(bytes.duplicate()))
這裏不管option或either都提升了極好的靈活性, 在Java中若是要返回一個有兩種可能性的值就比較不那麼優雅了, 參考上面的連接
Scala支持這種隱式轉換,
implicit def intToString(x: Int) = x.toString //定義轉換函數後, 編譯器會自動作轉換
好比x op y沒法經過類型檢查, int x 不支持op操做, 而string支持, 編譯器就會自動調用上面的轉換函數, 將int x轉化爲string x
隱式參數
用法也比較詭異, 看例子
object Greeter { def greet(name: String)(implicit prompt: PreferredPrompt) {//聲明2個參數, 第二個是能夠隱式的,固然你也能夠顯式的寫 println("Welcome, "+ name +". The system is ready.") println(prompt.preference) } } object JoesPrefs { implicit val prompt = new PreferredPrompt("Yes, master> ") //聲明成implicit, 可用做補充 } import JoesPrefs._ scala> Greeter.greet("Joe") //編譯器會自動補充成, greet("Joe")(prompt) Welcome, Joe. The system is ready. Yes, master>
可變的同類對象序列, 適用於OO場景
val greetStrings = new Array[String](3) //greetStrings爲val, 可是內部的數組值是可變的
greetStrings(0) = "Hello" //scala用()而非[]
greetStrings(1) = ", "
greetStrings(2) = "world!\n"
for (i <- 0 to 2)
print(greetStrings(i))
Scala 操做符等價於方法, 因此任意方法均可以以操做符的形式使用
1 + 2 //(1).+(2), 在只有一個參數的狀況下, 能夠省略.和()
0 to 2 //(0).to(2)
greetStrings(0) //greetStrings.apply(0),這也是爲何scala使用(), 而非[]
greetStrings(0) = "Hello" //greetStrings.update(0, "Hello")
簡化的array初始化
val numNames = Array("zero", "one", "two") //Array.apply("zero", "one", "two")
相對於array, List爲不可變對象序列, 適用於FP場景
val oneTwo = List(1, 2)
val threeFour = List(3, 4)val zeroOneTwo = 0 :: oneTwo //::
val oneTwoThreeFour = oneTwo ::: threeFour
對於List最經常使用的操做符爲::, cons, 把新的elem放到list最前端
:::, 兩個list的合併
右操做數, ::
普通狀況下, 都是左操做數, 好比, a * b => a.*(b)
可是當方法名爲:結尾時, 爲右操做數
1 :: twoThree => twoThree.::(1)
不支持append
緣由是, 這個操做的耗時會隨着list的長度變長而線性增加, 因此不支持, 只支持前端cons, 實在須要append能夠考慮ListBuffer
支持模式
scala> val List(a, b, c) = fruit a: String = apples b: String = oranges c: String = pears scala> val a :: b :: rest = fruit a: String = apples b: String = oranges rest: List[String] = List(pears)
list zip,齧合
scala> abcde.indices zip abcde //indices返回全部有效索引值 res14: List[(Int, Char)] = List((0,a), (1,b), (2,c), (3,d),(4,e)) scala> val zipped = abcde zip List(1, 2, 3) zipped: List[(Char, Int)] = List((a,1), (b,2), (c,3)) //會自動截斷
Folding lists: /: and :\
摺疊操做, 比較奇怪的操做, 看例子
(z /: List(a, b, c)) (op) equals op(op(op(z, a), b), c) scala> def sum(xs: List[Int]): Int = (0 /: xs) (_ + _) //實現列表元素疊加 (List(a, b, c) :\ z) (op) equals op(a, op(b, op(c, z))) def flattenLeft[T](xss: List[List[T]]) = (List[T]() /: xss) (_ ::: _) //兩種的區別 def flattenRight[T](xss: List[List[T]]) = (xss :\ List[T]()) (_ ::: _)
import scala.collection.immutable.Queue //不可變Queue
val empty = new Queue[Int] val has1 = empty.enqueue(1) //添加單個元素 val has123 = has1.enqueue(List(2, 3)) //添加多個元素 val (element, has23) = has123.dequeue //取出頭元素,返回兩個值, 頭元素和剩下的queue element: Int = 1 has23: scala.collection.immutable.Queue[Int] = Queue(2,3)
import scala.collection.mutable.Queue //可變Queue val queue = new Queue[String] queue += "a" //添加單個 queue ++= List("b", "c") //添加多個 queue.dequeue //取出頭元素, 只返回一個值 res22: String = a scala> queue res23: scala.collection.mutable.Queue[String] = Queue(b, c)
import scala.collection.mutable.Stack val stack = new Stack[Int] stack.push(1) stack.push(2) scala> stack.top res8: Int = 2 scala> stack.pop res10: Int = 2 scala> stack res11: scala.collection.mutable.Stack[Int] = Stack(1)
tuple和list同樣是不可變的, 不一樣是, list中的elem必須是同一種類型, 但tuple中能夠包含不一樣類型的elem
val pair = (99, "Luftballons") //自動推斷出類型爲,Tuple2[Int, String] println(pair._1) //從1開始,而不是0,依照Haskell and ML的傳統 println(pair._2) //elem訪問方式不一樣於list, 因爲元組中elem類型不一樣
var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
println(jetSet.contains("Cessna"))
val treasureMap = Map[Int, String]() treasureMap += (1 -> "Go to island.") treasureMap += (2 -> "Find big X on ground.") println(treasureMap(2))
val romanNumeral = Map(1 -> "I", 2 -> "II", 3 -> "III" ) //簡寫
Scala須要兼顧OO和FP, 因此須要提供mutable和immutable版本
這裏默認是Immutable, 若是須要使用mutable版本, 須要在使用前顯示的引用...
import scala.collection.mutable.Set
import scala.collection.mutable.Map
Sorted Set and Map
和Java同樣, Scala也提供基於紅黑樹實現的有序的Set和Map
import scala.collection.immutable.TreeSet scala> val ts = TreeSet(9, 3, 1, 8, 0, 2, 7, 4, 6, 5) ts: scala.collection.immutable.SortedSet[Int] = Set(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) scala> val cs = TreeSet('f', 'u', 'n') cs: scala.collection.immutable.SortedSet[Char] = Set(f, n, u) import scala.collection.immutable.TreeMap scala> var tm = TreeMap(3 ->'x', 1 ->'x', 4 ->'x') scala> tm += (2 ->'x') scala> tm res38: scala.collection.immutable.SortedMap[Int,Char] = Map(1 ->x, 2 ->x, 3 ->x, 4 ->x)
相對於Java定義比較簡單, 默認public
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
}def checksum(): Int = {
return ~(sum & 0xFF) + 1
}
}
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = sum += b
def checksum(): Int = ~(sum & 0xFF) + 1
}
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte) { sum += b } //對於Unit返回的, 另外一種簡寫, 用{}來表示無返回, 因此前面的就不用寫了
def checksum(): Int = ~(sum & 0xFF) + 1
}
實例化
val acc = new ChecksumAccumulator
val csa = new ChecksumAccumulator
acc.sum = 3
Singleton對象
Scala不能定義靜態成員, 因此用Singleton對象來達到一樣的目的
import scala.collection.mutable.Map object ChecksumAccumulator { //用object代替class private val cache = Map[String, Int]() def calculate(s: String): Int = if (cache.contains(s)) cache(s) else { val acc = new ChecksumAccumulator for (c <s) acc.add(c.toByte) val cs = acc.checksum() cache += (s -> cs) cs } }
最多見的場景就是, 做爲scala程序的入口,
To run a Scala program, you must supply the name of a standalone singleton object with a main method that takes one parameter(Array[String]), and has a result type of Unit.
import ChecksumAccumulator.calculate object Summer { def main(args: Array[String]) { for (arg <args) println(arg +": "+ calculate(arg)) } }
不可變對象
適用於FP場景的對象, 因此也叫作Functional Objects.
好處, 消除可變帶來的複雜性, 能夠放心的當參數傳遞, 多線程下使用啊...
下面以定義有理數類爲例
class Rational(n: Int, d: Int) //極簡方式,沒有類主體
和上面的定義比, 不需定義成員變量, 而只是經過參數, 由於根本沒有定義成員變量, 因此無處可變.
class Rational(n: Int, d: Int) { require(d != 0) //Precondition, 若是require返回false會拋出IllegalArgumentException,阻止初始化 private val g = gcd(n.abs, d.abs) val numer = n / g //添加不可變成員字段,便於引用 val denom = d / g def this(n: Int) = this(n, 1) //輔助構造函數 def + (that: Rational): Rational = //定義操做符 new Rational( numer * that.denom + that.numer * denom, denom * that.denom //也可使用this.number引用成員 ) def + (i: Int): Rational = //典型的成員函數重載 new Rational(numer + i * denom, denom) override def toString = numer +"/"+ denom //override, 方法重載 private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) }
可變對象
可變Objects自己沒啥好說的, 說說Scala getter和setter規則
every var that is a non-private member of some object implicitly defines a getter and a setter method with it.
The getter of a var x is just named 「x」, while its setter is named 「x_=」.
每一個非私有的var都會隱含的自動定義getter和setter, 以下面的例子
class Time { var hour = 12 var minute = 0 } //等同於 class Time { private[this] var h = 12 private[this] var m = 0 def hour: Int = h def hour_=(x: Int) { h = x } def minute: Int = m def minute_=(x: Int) { m = x } }
因此在Scala中比較有趣的是, 其實你能夠不真正定義這個成員, 而只須要定義getter和setter就能夠
以下面的例子, 並無真正定義華氏溫度, 而只是定義了getter和setter, 更簡潔
class Thermometer { var celsius: Float = _ def fahrenheit = celsius * 9 / 5 + 32 def fahrenheit_= (f: Float) { celsius = (f 32) * 5 / 9 } override def toString = fahrenheit +"F/"+ celsius +"C" }
抽象類
abstract class Element { //abstract表示抽象類 def contents: Array[String] //沒有函數體表示聲明抽象方法 }
無參函數和屬性, 統一訪問模式
客戶能夠用一樣的方式來訪問函數width或屬性width, 這樣的好處是width改變不一樣的實現方式時, 不會影響客戶代碼, 實現統一訪問模式
函數和屬性的區別, 函數節省空間而須要每次計算, 屬性是初始化時算好的, 比較快, 但須要額外的空間, 因此可用在不一樣場景下選擇不一樣實現
abstract class Element { def contents: Array[String] //組合Array[] def height: Int = contents.length //無參函數 def width: Int = if (height == 0) 0 else contents(0).length } //等同於 abstract class Element { def contents: Array[String] val height = contents.length //屬性 val width = if (height == 0) 0 else contents(0).length }
Scala爲了實現這種統一的訪問模式, 通常對於無參函數在調用時能夠省掉(), 但前提是該無參函數是沒有反作用的, 不然就會比較confusing
"hello".length // no () because no sideeffect println() // better to not drop the (),由於有反作用
繼承
超類的私有成員不會被子類繼承.
抽象成員, 須要被子類實現(implement)
通常成員, 可用被子類重寫(override)
override關鍵字的使用,
重寫時, 必須顯式加override, 防止意外的重寫
實現時, 是否寫override可選
其餘狀況禁止使用
class ArrayElement(conts: Array[String]) extends Element { def contents: Array[String] = conts //實現抽象函數 }
這個圖, 反映出繼承和組合, 其中Element繼承自AnyRef, 而ArrayElement組合Array[]
重寫方法和屬性
這是scala比較特別的地方, 方法和屬性在同一個命名空間, 因此方法和屬性不能同名
而且支持, 在override時, 方法和屬性間可用互相轉換
前面將contents聲明成方法, 是否以爲有些彆扭, 看上去更像屬性, 是的, 你可用在子類中實現或重寫成屬性
在scala中, 無反作用的無參函數和屬性是能夠簡單的互相轉換
class ArrayElement(conts: Array[String]) extends Element { val contents: Array[String] = conts //將方法contents實現成屬性 }
Java和Scala的命名空間對比
Java’s four namespaces are fields, methods, types, and packages.
By contrast, Scala’s two namespaces are:
• values (fields, methods, packages, and singleton objects)
• types (class and trait names)
參數化屬性
一種scala的簡化,
是否以爲, 定義參數, 再將參數賦值給屬性, 很麻煩, ok, scala可用簡化
class ArrayElement( val contents: Array[String] ) extends Element //實現屬性
另外一個例子,
class Cat { val dangerous = false } class Tiger( override val dangerous: Boolean, //重寫屬性 private var age: Int //增長新的屬性 ) extends Cat
超類構造器
和C++或Pythno同樣, 須要在子類中顯式的構造超類, 好比這裏須要先構造ArrayElement
只不過Scala提供一種簡寫的方式, 而不用在代碼中另寫一行
class LineElement(s: String) extends ArrayElement(Array(s)) { override def width = s.length override def height = 1 }
final成員
class ArrayElement extends Element { final override def demo() { //該成員函數禁止被任何子類override println("ArrayElement's implementation invoked") } } final class ArrayElement extends Element {//ArrayElement禁止被任何子類繼承 override def demo() { println("ArrayElement's implementation invoked") } }
工廠對象實現
object Element { //用伴生對象(Singleton)來作工廠對象 def elem(contents: Array[String]): Element = new ArrayElement(contents) def elem(chr: Char, width: Int, height: Int): Element = new UniformElement(chr, width, height) def elem(line: String): Element = new LineElement(line) }
At the top of the hierarchy is class Any.
Any聲明以下接口, 故全部類都支持這些接口,
final def ==(that: Any): Boolean final def !=(that: Any): Boolean def equals(that: Any): Boolean def hashCode: Int def toString: String
Any有兩個子類, AnyVal和AnyRef
AnyVal是全部built-in value class的父類, 除了常見的基本類型外, 還有Unit
AnyRef是全部reference classes的父類, 其實就是java.lang.Object
Scala還定義, Nothing和Null class
Nothing是全部Any的子類, 而Null是全部AnyRef的子類
幹嘛用的?用於以統一的方式來處理邊界形式或異常返回, 由於他們是全部類的子類, 全部可用返回給任意返回類型
例子,
def error(message: String): Nothing = throw new RuntimeException(message) def divide(x: Int, y: Int): Int = //返回類型爲Int if (y != 0) x / y else error("can't divide by zero") //異常時返回Nothing
支持package嵌套和經過{}指定範圍
package launch { class Booster3 } package bobsrockets { package navigation { class Navigator } package launch { class Booster { // No need to say bobsrockets.navigation.Navigator val nav = new navigation.Navigator val booster3 = new _root_.launch.Booster3 //_root_特指頂層包 } } }
Import的用法
import bobsdelights.Fruit import bobsdelights._ //import all import Fruits.{Apple, Orange} //指定import import Fruits.{Apple => McIntosh, Orange} //別名 import Fruits.{Apple => _, _} //排除Apple
Private
基本和Java一致,可是在對inner class上表現出更好的一致性
Java容許外部類訪問內部類的private成員,而scala不行,以下例子中的error case
class Outer { class Inner { private def f() { println("f") } class InnerMost { f() // OK } } (new Inner).f() // error: f is not accessible }
Protected
一樣比Java更嚴格一些,僅容許在子類中被訪問
而Java容許同一個包中的其餘類訪問該類的protected成員,而scala中不行,以下面的error case
package p { class Super { protected def f() { println("f") } } class Sub extends Super { f() } class Other { (new Super).f() // error: f is not accessible } }
修飾符做用域
scala中的修飾符能夠加上做用域,很是靈活
private[X] or protected[X] means that access is private or protected 「up to」 X, where X designates some enclosing package, class or singleton object.
加上做用域的意思,除了原來private和protect表示的可見範圍外,將可見範圍擴展到X,X能夠是package,class,singleton對象
package bobsrockets { package navigation { private[bobsrockets] class Navigator { //使Navigator類在bobsrockets package中可見,因此在package launch中能夠new Navigator protected[navigation] def useStarChart() {} //在Navigator和子類,以及package Navigation中能夠被訪問(等同於Java) class LegOfJourney { private[Navigator] val distance = 100 //是私有變量distance,在外部類Navigtor中可見(等同於Java) } private[this] var speed = 200 //僅僅在相同對象中能夠訪問,比private的相同類訪問還嚴格,以下例 val other = new Navigator other.speed // this line would not compile } } package launch { import navigation._ object Vehicle { private[launch] val guide = new Navigator } } }
伴生對象
A class shares all its access rights with its companion object and vice versa.
概念上相似Java的interface, 可是更強大, 能夠有方法實現, 屬性...
其實幾乎等同於class, 除了兩點,
a. trait不能有任何參數
b. trait中super是動態綁定的, 由於定義的時候根本就不知道super是誰
trait Philosophical {
def philosophize() {
println("I consume memory, therefore I am!")
}
}
mix in有兩種方式, extend和with
extend, implicitly inherit the trait’s superclass
因此下面的例子, Frog會繼承Philosophical的超類AnyRef
class Frog extends Philosophical { //mix in Philosophical trait override def toString = "green" }
with, 須要顯式的指明超類
class Animal trait HasLegs class Frog extends Animal with Philosophical with HasLegs {//繼承Animal,並mix in兩個traits override def toString = "green" }
Trait真正的做用在於, 模塊封裝, 能夠把相對獨立的功能封裝在trait中, 並在須要的時候進行mix in, 以下面的例子
//傳統的例子 class Rational(n: Int, d: Int) { // ... def < (that: Rational) = this.numer * that.denom > that.numer * this.denom def > (that: Rational) = that < this def <= (that: Rational) = (this < that) || (this == that) def >= (that: Rational) = (this > that) || (this == that) } //將比較接口和實現封裝在Ordered Trait中,這裏僅僅須要mix in和實現compare class Rational(n: Int, d: Int) extends Ordered[Rational] { // ... def compare(that: Rational) = (this.numer * that.denom) ( that.numer * this.denom) }
Traits as stackable modifications
trait的強大還體如今能夠同時mix in多個traits, 並以相似stackable形式的線性調用, 即多個trait會效果疊加
而傳統的多重繼承, 只有一個實現會有效
abstract class IntQueue { def get(): Int def put(x: Int) } import scala.collection.mutable.ArrayBuffer class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { buf += x } } trait Doubling extends IntQueue { //trait繼承自IntQueue, 說明該trait只能mix in IntQueue(或子類)中 //注意這裏super的使用(動態綁定), 和abstract override(僅用於這種場景) //表示trait must be mixed into some class that has a concrete definition of the method in question abstract override def put(x: Int) { super.put(2 * x) } } trait Incrementing extends IntQueue { abstract override def put(x: Int) { super.put(x + 1) } } trait Filtering extends IntQueue { abstract override def put(x: Int) { if (x >= 0) super.put(x) } }
下面來看看, 怎麼樣進行stackable的調用
//同時mix in了Incrementing和Filtering //注意調用順序是, 從右到左(stackable) //這個例子, 先調用Filtering, 再調用Incrementing, 順序不一樣會帶來不一樣的結果 scala> val queue = (new BasicIntQueue with Incrementing with Filtering) queue: BasicIntQueue with Incrementing with Filtering... scala> queue.put(-1); queue.put(0); queue.put(1) scala> queue.get() res15: Int = 1 scala> queue.get() res16: Int = 2
To trait, or not to trait
If the behavior will not be reused, then make it a concrete class.
If it might be reused in multiple, unrelated classes, make it a trait.
If you want to inherit from it in Java code, use an abstract class.
If you plan to distribute it in compiled form, and you expect outside groups to write classes inheriting from it, you might lean towards using an abstract class.
If efficiency is very important, lean towards using a class.
成員函數, OO的方式
內部函數, 須要切分功能, 又不想污染外部的命名空間
First-class function, unnamed function literal
function象變量同樣, 能夠被賦值和當參數傳遞, 但在scala須要以function literal的形式, 在運行期的時候會實例化爲函數值(function value)
scala> var increase = (x: Int) => x + 1 scala> increase(10)
Partially applied functions
scala> def sum(a: Int, b: Int, c: Int) = a + b + c scala> val a = sum _ //用佔位符代替整個參數列表 scala> a(1, 2, 3) //a.apply(1, 2, 3) res13: Int = 6 scala> val b = sum(1, _: Int, 3) //partial function, 用佔位符代替一個參數 scala> b(2) res15: Int = 6
Closures
關於閉包的解釋,
對於一般的function, (x: Int) => x + 1, 稱爲closed term
而對於(x: Int) => x + more, 稱爲open term
因此對於開放的, 必須在定義的時候對裏面的自由變量more動態進行綁定, 因此上下文中必需要有對more的定義, 這種關閉open term過程產生了closure
scala> var more = 1 scala> val addMore = (x: Int) => x + more //產生閉包,綁定more scala> addMore(10) res19: Int = 11 scala> more = 9999 scala> addMore(10) res21: Int = 10009 //可見閉包綁定的不是value,而是變量自己
剛看到有些驚訝, 去clojure裏面試一下, 也是這樣的, 綁定的變量自己, 閉包會取最新的值
固然通常不會這樣使用閉包.
下面這個例子, 是較經常使用的case, 其中閉合了函數的參數
如何在閉包調用時, 能夠訪問到已經不存在的變量? 當產生閉包時, 編譯器會將這個變量從堆棧放到堆裏面, 因此函數結束後還能訪問
def makeIncreaser(more: Int) = (x: Int) => x + more
scala> val inc1 = makeIncreaser(1)
scala> val inc9999 = makeIncreaser(9999)
scala> inc1(10)
res24: Int = 11
scala> inc9999(10)
res25: Int = 10009
先提供sum的兩個版本的比較,
scala> def plainOldSum(x: Int, y: Int) = x + y
scala> plainOldSum(1, 2)
res4: Int = 3
scala> def curriedSum(x: Int)(y: Int) = x + y //currying版本的sum
curriedSum: (Int)(Int)Int
scala> curriedSum(1)(2)
res5: Int = 3
其實currying, 等同於調用兩次function, first會返回第二個函數的函數值, 其中closure了x
scala> def first(x: Int) = (y: Int) => x + y first: (Int)(Int) => Int
取出函數值, 效果是減小了參數個數, 第一個函數的參數已經closure在第二個函數中了, 和partially有些相似(區別)
scala> val onePlus = curriedSum(1)_ onePlus: (Int) => Int = <function> scala> onePlus(2) res7: Int = 3
有什麼用? 用於建立更像built-in的控制結構
以下, 使用{}更像built-in, 但{}有個限制是, 只有單個參數的參數列表能夠用{}替換(), 因此這個時候須要用currying來下降參賽個數
scala> println("Hello, world!") //象方法調用 scala> println { "Hello, world!" } //更像built-in的控制結構,好比if
對於FP, 相對於OO使用繼承和多態, 使用函數做爲參數來實現代碼重用, 但願能夠將函數值放在{}, 顯得更象built-in
好比下面, 每次打開文件, 操做, 關閉文件, 固定模式, 因此實現withPrintWriter, 每次傳入不一樣的op就能夠進行不一樣的操做, 而不用考慮文件開關
若是是oo實現, 就須要傳入基類對象, 利用多態實現, 明顯使用函數更輕量級一些
def withPrintWriter(file: File, op: PrintWriter => Unit) { val writer = new PrintWriter(file) try { op(writer) } finally { writer.close() } } //以調用方法的方式使用 withPrintWriter( new File("date.txt"), writer => writer.println(new java.util.Date) )
經過currying減小了參數, 因此就可使用{}
def withPrintWriter(file: File)(op: PrintWriter => Unit) {......} //currying版本 val file = new File("date.txt") withPrintWriter(file) { writer => writer.println(new java.util.Date) } //將函數值放在{}, 很像built-in
前面說了, FP儘可能避免使用循環, 而應該使用遞歸
可是遞歸效率有問題, 不停的壓棧, 也很容易爆堆棧
因此對於某種遞歸, 尾遞歸, 編譯器會自動優化成循環執行, 避免屢次使用堆棧
侷限是, 不是什麼狀況都能寫成尾遞歸, 其實只有循環能夠...
比clojuer好, 編譯器會自動進行優化
//while, 循環版本,oo def approximateLoop(initialGuess: Double): Double = { var guess = initialGuess while (!isGoodEnough(guess)) guess = improve(guess) guess } //遞歸版本,FP def approximate(guess: Double): Double = if (isGoodEnough(guess)) guess else approximate(improve(guess))
pattern matching (模式匹配是FP裏面比較高級的特性), 能夠參考一下clojure的Multimethods
那麼Scala要如何來支持pattern matching?
Case Class
首先case class是用於讓你更加簡潔的使用pattern matching, 若是你想對一個class進行pattern matching, 最好在前面加上case, 以下面的例子(string和double的一元或二元操做)
abstract class Expr case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
那麼成爲case class, 如何就能便於pattern matching?
1. 添加與類名同樣的工廠方法, 即建立類對象時不須要new, 更簡潔
scala> val v = Var("x") //替代new Var("x")
scala> val op = BinOp("+", Number(1), v)//new BinOp("+", new Number(1), v)
2. all arguments in the parameter list of a case class implicitly get a val prefix, so they are maintained as fields. 參數會默認隱含的加上val前綴, 因此能夠看成field來用
scala> v.name res0: String = x scala> op.left res1: Expr = Number(1.0)
3. the compiler adds 「natural」 implementations of methods toString, hashCode, and equals to your class. 意味着能夠用於println和"=="
scala> println(op) BinOp(+,Number(1.0),Var(x)) scala> op.right == Var("x") res3: Boolean = true
case class帶來的反作用就是你的類會比原來的大一些
Pattern matching
下面給個例子來講明, 實現方式很是相似switch…case
match關鍵字代表匹配前面的expr
每一個case, case開頭, 跟着須要匹配的模式, =>後面加上執行語句
下面的例子的意思, 簡化expr, 當出現兩個-號, +0, 或*1的狀況下, 就直接返回變量e就能夠
def simplifyTop(expr: Expr): Expr = expr match { case UnOp("-", UnOp("-", e)) => e //UnOp構造器匹配(匹配類型和參數), "-"常量匹配(使用==), e變量匹配(任意值,並方便後面引用) case BinOp("+", e, Number(0)) => e case BinOp("*", e, Number(1)) => e // Multiplying by one case _ => expr //-通配符匹配, 匹配任意值,但沒法引用 }
和switch case的區別
1. match是scala中的expression, 因此必定會返回值
2. 不用加break, 匹配即返回
3. 若是沒有匹配上, 會拋MatchError異常, 因此你必須考慮到全部case, 或加上默認case
Pattern種類
Wildcard patterns, 通配符模式
catch all, 用於忽略你不關心的部分
expr match { case BinOp(_, _, _) => println(expr +"is a binary operation") case _ => println("It's something else") }
Constant patterns, 常量模式
Any literal may be used as a constant. For example, 5, true, and "hello" are all constant patterns.
Also, any val or singleton object can be used as a constant.
def describe(x: Any) = x match { case 5 => "five" case true => "truth" case "hello" => "hi!" case Nil => "the empty list" //單例對象Nil匹配空列表 case _ => "something else" }
Variable patterns, 變量模式
變量模式, 和通配符同樣是catch all, 可是區別就在於, 後面能夠經過變量引用
expr match { case 0 => "zero" case somethingElse => "not zero: "+ somethingElse }
變量模式和常量模式的區別
scala> import Math.{E, Pi} import Math.{E, Pi} scala> E match { case Pi => "strange math? Pi = "+ Pi //Pi是常量而非變量 case _ => "OK" } res10: java.lang.String = OK全部小寫字母開頭的爲變量模式, 其餘的都是常量模式
若是要用小寫字母開頭表示變量, 兩種方法, 加前綴this.pi, 加反引號`pi`
Constructor patterns, 構造器模式
這個模式使得scala的模式匹配顯得格外強大, 由於這樣能夠deep match
好比下面的例子, 能夠匹配BinOP類, 類參數+, e, Number類, Number類的參數0, 能夠達到3層的deep match
expr match { case BinOp("+", e, Number(0)) => println("a deep match") case _ => }
Sequence patterns, 序列模式
expr match { case List(0, _, _) => println("found it") //第一個爲0的length爲3的List case _ => } expr match { case List(0, _*) => println("found it") //第一個爲0,可變長的List case _ => }
Tuple patterns, 元組模式
特別之處在於, 能夠用tuple給多個變量賦值
def tupleDemo(expr: Any) = expr match { case (a, b, c) => println("matched "+ a + b + c) case _ => }
Typed patterns, 類型模式
須要注意的是, 在類型模式下用s替換x
緣由在於, x的類型是Any, 因此你沒法調用x.length, 因此必須使用string s來替換x
def generalSize(x: Any) = x match { case s: String => s.length case m: Map[_, _] => m.size //匹配任意Map case _ => -1 }
能夠看到這裏很方便, 若是你對比一下使用傳統的isInstanceOf and asInstanceOf的方式
Type erasure, 類型擦除
能夠看到運行時, 編譯器是沒法check Map中數據的類型的, 由於Scala和Java同樣使用了erasure model of generics(泛型的擦除模式), 參數類型信息沒有保留到運行期
scala> def isIntIntMap(x: Any) = x match { case m: Map[Int, Int] => true //會報unchecked warning, 由於沒法check Map中是否爲Int case _ => false } scala> isIntIntMap(Map(1 >1)) res17: Boolean = true scala> isIntIntMap(Map("abc" >"abc")) res18: Boolean = true //能夠看出, 實際上是沒法區分Map中數據類型的
可是, 數組是惟一的意外, 由於他們被特殊實現, 在運行期會保留參數類型信息,
scala> def isStringArray(x: Any) = x match { case a: Array[String] => "yes" case _ => "no" } scala> val ai = Array(1, 2, 3) scala> isStringArray(ai) res20: java.lang.String = no //對於數組能夠識別出
Variable binding, 變量綁定
經過@將UnOp("abs", _)綁定到變量e上
expr match { case UnOp("abs", e @ UnOp("abs", _)) => e case _ => }
Pattern guards
其實就是在模式後面加上必定的條件判斷, 經過了纔會執行後面的語句
好比下面的例子, 判斷x是否等於y,
scala> def simplifyAdd(e: Expr) = e match { case BinOp("+", x, y) if x == y => BinOp("*", x, Number(2)) //加上條件判斷, 稱爲pattern guard case _ => e }
Pattern overlaps
Scala支持多個Pattern重疊, 好比下面的例子, 匹配第一個的, 必定也會匹配第二個, 第三個
但順序不能反, 必須先具體的, 後general的, 不然編譯器會報錯
def simplifyAll(expr: Expr): Expr = expr match { case UnOp("-",UnOp("-",e)) => simplifyAll(e) // ‘-’is its own inverse case UnOp(op, e) => UnOp(op, simplifyAll(e)) case _ => expr }
Sealed classes
Sealed意味着只能在這個文件定義子類, 這樣就不能隨便在其餘地方增長case類
這樣作的緣由是, scala要求模式匹配時須要考慮到全部的狀況, 因此程序員都須要知道到底有哪些case class
經過Sealed class, 不但程序員能夠在該文件中找到全部的case class, 並且scala編譯器也會找到, 並在編譯時作檢查,
sealed abstract class Expr //sealed case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
def describe(e: Expr): String = (e: @unchecked) match { //@unchecked告訴編譯器不用check這個match的case case Number(_) => "a number" case Var(_) => "a variable" }
在處理併發上, Scala採起了和Clojure徹底不一樣的思路, 採起actor模式儘可能避免併發衝突的可能性, 用於補充Java傳統的併發模式
Actor模式就不解釋了, 這裏actor就是一個線程
下面定義的echoActor, 會不停的讀inbox, 並打印出收到的message
val echoActor = actor { while (true) { receive { case msg => println("received message: "+ msg) } } }
而後, 如何給actor發消息? 很簡單, 以下
scala> echoActor ! "hi there" received message: hi there //echoActor線程收到message, 並打印出來
而且這裏receive只會接收匹配的message(即匹配receive中的case的), 對於不匹配的message不會去處理
在scala中也能夠將native線程看成actor來用, 而且經過Actor.self來當前線程看成actor看
import scala.actors.Actor._ scala> self ! "hello" //給當前線程actor發送消息 scala> self.receive { case x => x } //當前線程receive res6: Any = hello
這裏須要先發message, 再recieve, 由於receive是block函數, 沒有message不會返回
固然也可使用receiveWithin, 來設置timeout
scala> self.receiveWithin(1000) { case x => x } // wait a sec! res7: Any = TIMEOUT
actor模式問題是, 每一個actor都須要一個線程, 因此若是須要定義大量的actor效率會比較低
JVM只能建立幾千個線程, 而且線程間的切換也是比較低效的
因此使用react方法能夠共享線程以提升效率...
原理在於, Scala actor模塊會維護一個線程池, 用於分配線程給每一個線程
對於receive方法, 分配的線程會一直被actor佔住, 由於receive會返回值須要保持當前的調用棧
而react方法, 不會返回, 不須要保持調用棧, 因此執行完這個線程就能夠被釋放, 用於其餘的actor
object NameResolver extends Actor { import java.net.{InetAddress, UnknownHostException} def act() { react { case (name: String, actor: Actor) => actor ! getIp(name) act() case "EXIT" => println("Name resolver exiting.")// quit case msg => println("Unhandled message: "+ msg) act() } } def getIp(name: String): Option[InetAddress] = { try { Some(InetAddress.getByName(name)) } catch { case _:UnknownHostException => None } } }
How react works
A return type of Nothing indicates a function will never return normally, but instead will always complete abruptly with an exception. And indeed, this is true of react.
react的返回值是Nothing, 意思是不會正常返回, 即以異常做爲react的結束
The actual implementation of react is not as simple as the following description, and subject to change, but conceptually you can think of react as working like this:
When you call start on an actor, the start method will in some way arrange things such that some thread will eventually call act on that actor. If that act method invokes react, the react method will look in the actor’s mailbox for a message the passed partial function can handle. (It does this the same way as receive, by passing candidate messages to the partial function’s isDefinedAt method.)
前面都是和receive同樣的, 線程調用act, act調用react從actor的mailbox裏面check是否有須要處理的message
If it finds a message that can be handled, react will schedule the handling of that message for later execution and throw an exception.
If it doesn’t find one, it will place the actor in 「cold storage,」 to be resurrected if and when it gets more messages in its mailbox, and throw an exception.
In either case, react will complete abruptly with this exception, and so will act. The thread that invoked act will catch the exception, forget about the actor, and move on to other duties.
若是找到須要處理的message, react就會處理這條message, 最終拋出exception, 若是找不到合適的message, 會將這個actor暫時休眠, 並拋出exception
能夠看到, 這裏的關鍵在於, react必定會以exception結束, 最終調用act的線程會catch到這個exception, 知道react已經完成, 因而接着處理其餘的事情, 因此達到線程重用
This is why if you want react to handle more than the first message, you’ll need to call act again from inside your partial function, or use some other means to get react invoked again.
使用Actor.Loop來反覆執行消息處理, 而不能用while
def act() { loop { react { case (name: String, actor: Actor) => actor ! getIp(name) case msg => println("Unhandled message: " + msg) } } }
主Actor不該被阻塞, 由於這樣他就不能及時的響應其餘消息
處理的方法, 就是阻塞邏輯放到一個新的actor裏面執行, 由於這個actor不會接收其餘消息, 因此被阻塞是沒有影響的
val sillyActor2 = actor { def emoteLater() { val mainActor = self actor { //使用新的actor Thread.sleep(1000) //阻塞邏輯 mainActor ! "Emote" //將結果返回給主actor } } var emoted = 0 emoteLater() loop { react { case "Emote" => println("I'm acting!") emoted += 1 if (emoted < 5) emoteLater() //這裏沒有直接執行阻塞邏輯, 而是調用新的actor來執行 case msg => println("Received: "+ msg) } } }
實際運行結果, 可見發送給sillyActor2的消息當即會獲得處理, 不會被阻塞
scala> sillyActor2 ! "hi there"
scala> Received: hi there
I'm acting!
I'm acting!
I'm acting!
經過case class來定義消息格式
不然若是隻是簡單的, lookerUpper ! ("www.scalalang.org", self), 其餘人很難理解你的消息
import scala.actors.Actor._ import java.net.{InetAddress, UnknownHostException} case class LookupIP(name: String, respondTo: Actor) //經過case class來定義消息格式, 來便於理解 case class LookupResult( name: String, address: Option[InetAddress] ) object NameResolver2 extends Actor { def act() { loop { react { case LookupIP(name, actor) => actor ! LookupResult(name, getIp(name)) } } } def getIp(name: String): Option[InetAddress] = { // As before (in Listing 30.3) } }