Scala的虛無與飄渺
本文名字與內容無關。
很多人抱怨Scala相比于Java過于復雜了:大部分使用過Scala的程序員都沒有能深刻理解它的類型系統和Scala的函數式編程。Scala的類型系統跟Java和C++很不一樣,Scala想把面向對象純粹化(學院派的作風?),不能有破壞面向對象的一切因素出現。null、NULL、int、...這些都是不和諧的東西,應該割掉。Scala又給了太多了空間給程序員,你可以使用傳統的命令式編程風格,也可以使用函數式風格,一個語言寫出了不同的代碼。這個漢語有很多方言,極為相似。太多的自由會浪費Scala的一片好心。一個思路是在封裝完Java的功能后,盡量使用Scala推薦的函數式風格來寫面向對象的程序。
進入Scala,你就進入了虛無縹緲的太虛境地。在何為有?何為無?的問題上?Scala的設計走得很遠。Scala的有即Any,Scala的無是Null,null,Nil,Nothing,None,Unit。Scala的無太讓人手足無措,今天就討論Scala的無。
要想在正確的地方使用正確的無,就要先理解它們分別表示的含義。
Null&null
很多人一輩子都沒有走出這個無。Null是一個Trait,你不能創建她的實例。但是Scala在語言層面上存在一個Null的實例,那就是null。Java中的null意味著引用并沒有指向任何對象。但存在一個悖論,一切都是對象,那沒有對象是不是也是對象呢?Scala定義了一個類似于對象語義的Null,和一個值語義的null。這樣面向對象在空引用的情況下完備了。如果你寫了一個帶有Null作為參數的對象,那么你傳入的參數只能是null,或者指向Null的引用。
scala> tryit("hey")
<console>:6: error: type mismatch;
found : java.lang.String("hey")
required: Null
tryit("hey")
^
scala> val someRef: String = null
someRef: String = null
scala> tryit(someRef)
<console>:7: error: type mismatch;
found : String
required: Null
tryit(someRef)
^
scala> tryit(null)
That worked!
scala> val nullRef: Null = null
nullRef: Null = null
scala> tryit(nullRef)
That worked!
第四行我們試圖傳入一個String,自然不能工作。第14行我們傳入一個null引用,但是任然不能工作,為什么呢?因為null引用指向的是String類型。它可能在運行時是null,但是在編譯時類型檢查卻不認同,編譯器認為他是個String。
Nil
Nil是一個繼承List[Nothing]的對象,我們隨后討論Nothing。它就是一個空的列表,下面是一些使用實例:
scala> Nil
res4: Nil.type = List()
scala> Nil.length
res5: Int = 0
scala> Nil + "ABC"
res6: List[java.lang.String] = List(ABC)
scala> Nil + Nil
res7: List[object Nil] = List(List())
可以看出,Nil就是一個可以封裝任何東西的空容器。它的長度為0。它并不是一無所有,它是一個容器,一個列表,只是沒有存放內容而已。
Nothing
Nothing可能比較難理解。Nothing也是一個Trait,它繼承自Any,而Any是整個Scala類型系統的根。Nothing是沒有實例的,但它時任何對象的子類,他是List的子類,是String的子類,是Int的子類,是任何用戶自定義類型的子類。
前面提到Nil是一個空的List[Nothing]。由于Nothing是任何類型的子類,那么Nil就可以當做是一個空的String List,空的Int List,甚至使Any List。Nothing比較適合用來定義基類容器。
scala> val emptyStringList: List[String] = List[Nothing]()
emptyStringList: List[String] = List()
scala> val emptyIntList: List[Int] = List[Nothing]()
emptyIntList: List[Int] = List()
scala> val emptyStringList: List[String] = List[Nothing]("abc")
<console>:4: error: type mismatch;
found : java.lang.String("abc")
required: Nothing
val emptyStringList: List[String] = List[Nothing]("abc")
第一行,我們將一個List[Nothing]賦值給一個List[String]。一個Nothing是一個String,因此是正確的。第四行,我們將一個List[Nothing]賦值給一個List[Int]。一個Nothing是一個Int,因此是正確的。Nothing是任何類型的子類,但是上面的List[Nothing]都不包含任何成員。當我們創建一個包含一個String的List[Nothing]的List,并把它賦值給List[String],會出現什么情況?它會失敗,因為Nothing并不是任何類型的父類,并且也不存在Nothing的實例。任何Nothing的容器必然是空的,是Nil。
另一種Nothing的用法是作為不返回函數的返回值。因為Nothing沒有任何實例,而函數的返回值必定是一個值,是一個對象,這樣定義為Nothing為返回值的函數實際上不可能返回。
None
寫Java程序的時候,經常會碰到沒有有意義的東西可以返回,我們返回null。但返回null有一些問題,調用方必須檢查返回值,不然會有NullPointerException的異常。這逼迫我們去check函數的返回值。還有一種解決辦法是使用異常,但增加try/catch塊,并不是明智的選擇。
Scala內置一種解決辦法。如果你想返回一個String,但可能有的時候得不到有意義的返回值,我們可以讓函數返回Option[String]。
scala> def getAStringMaybe(num: Int): Option[String] = {
| if ( num >= 0 ) Some("A positive number!")
| else None // A number less than 0? Impossible!
| }
getAStringMaybe: (Int)Option[String]
scala> def printResult(num: Int) = {
| getAStringMaybe(num) match {
| case Some(str) => println(str)
| case None => println("No string!")
| }
| }
printResult: (Int)Unit
scala> printResult(100)
A positive number!
scala> printResult(-50)
No string!
函數getAStringMaybe返回Option[String]。Option是一個抽象類,它有兩個子類:Some和None。因此,初始化一個Option有兩種方法。getAStringMaybe返回的只可能是Some[String]或None。Some和None又是Case Class,可以直接用來做模式匹配,很容易用來處理返回值。
類似地,Option[T]作為返回值,意味著調用者可能會收到Some[T]或None。
其實Option作為返回值,并沒有比返回null好多少。代碼里面充滿了Option和模式匹配的代碼,也不優雅,所以對待Option還是慎重。
Unit
Unit跟java的void一樣,表示函數沒有返回值。
scala> def doThreeTimes(fn: (Int) => Unit) = {
| fn(1); fn(2); fn(3);
| }
doThreeTimes: ((Int) => Unit)Unit
scala> doThreeTimes(println)
1
2
3
scala> def specialPrint(num: Int) = {
| println(">>>" + num + "<<<")
| }
specialPrint: (Int)Unit
scala> doThreeTimes(specialPrint)
>>>1<<<
>>>2<<<
>>>3<<<
這個不解釋。
總的來看,Scala定義如此多的無,主要目的是為了保證它的類型系統的完整性,同時細化概念。到底好不好呢?下一個十年再下定論。
上述就是所有的Scala表征無的方式。如果你是Scala的簇擁,談談你的想法。
http://oldfashionedsoftware.com/2008/08/20/a-post-about-nothing/