Scala Puzzlers 是收集很多人貢獻的 scala 的題目,類似 Java Programmer 認證的考題,主要是確認 Programmer 是不是有了解 scala 語言的規格與特性。
Init You, Init Me 的問題是以下的 scala statements 會列印出什麼結果?
object XY {
object X {
val value: Int = Y.value + 1
}
object Y {
val value: Int = X.value + 1
}
}
println(if (math.random > 0.5) XY.X.value else XY.Y.value)
答案是 2
lazy, strict
首先我們要知道 scala 的保留字: lazy,當某個變數宣告為 lazy 時,則表示 scala 會等到要存取該變數時,才會初始化這個變數。跟 lazy 相反的是 strict,strict 表示會在定義時,就馬上將該變數初始化,scala 的 collection 中就有區分 lazy 與 strict 的兩種版本,因大量的 collection elements,並不一定會在定義時,就馬上需要使用到所有的 elements,因此不需要直接將 collection 所有元素初始化,等到需要使用時再處理。
如果以 recursive 的方式定義變數,一開始會發現 REPL 警告要定義資料型別。
scala> val x = y; val y = x
<console>:15: error: recursive value y needs type
val x = y; val y = x
^
當我們將 y 的資料型別,明確地定義出來,compiler 就接受我們宣告的 x 與 y 的定義,另外出現一個警告,告訴我們有個 statement 參考到了沒有定義初始值的 y,然後compiler 就自動將 y 的數值視為 0,所以將 x 也設為 0。
scala> val x: Int = y; val y = x
<console>:12: warning: Reference to uninitialized value y
val x: Int = y; val y = x
^
x: Int = 0
y: Int = 0
如果加上 lazy 的宣告,則 x 與 y 並不會在宣告時,就被初始化。
scala> lazy val x: Int = y; lazy val y = x
x: Int = <lazy>
y: Int = <lazy>
比較合理的使用方式,應該是在宣告 x 時,宣告為 lazy,但是讓 y 直接初始化,因為一開始可能不知道 y 的數值,後來知道了 y 時,再給予
scala> lazy val x: Int = y; val y=x; println("x="+x+", y="+y)
x=0, y=0
x: Int = <lazy>
y: Int = 0
另外因為 scala 規格中提到 "the value defined by an object definition is instantiated lazily",在 object 中定義的變數,scala 會自動以 lazy 的方式初始化。
結果不一定永遠是 2
因為題目只有運算過一次以下這個 statement
println(if (math.random > 0.5) XY.X.value else XY.Y.value)
如果我們多呼叫幾次,會發現結果也有可能會變成 1
scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
2
scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
1
scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
1
分析題目
把原本的題目 math.random 的部份用變數紀錄起來,會比較清楚處理的過程。
object XY {
object X {
val value: Int = Y.value + 1
}
object Y {
val value: Int = X.value + 1
}
}
val randomnumber=math.random
println( if (randomnumber > 0.5) XY.X.value else XY.Y.value )
當我們在 REPL 執行這些 statement時,可查看到結果為 2,在以下這個情況下,XY.Y.value 為 2 XY.X.value 為1,因為在使用 XY.Y.value 的時候,發現參考到 X.value,但 X 也還沒初始化,發現參考到 Y.value,這時發現 recursive 的狀況,就直接將 Y.value 視為 Integer 的初始值 0,接下來 XY.X.value 就等於 1,而 XY.Y.value 則為 2。
scala> object XY {
| object X {
| val value: Int = Y.value + 1
| }
| object Y {
| val value: Int = X.value + 1
| }
| }
defined object XY
scala> val randomnumber=math.random
randomnumber: Double = 0.24469087654673705
scala> println( if (randomnumber > 0.5) XY.X.value else XY.Y.value )
2
我們必須在 REPL 先執行 :reset,然後再運算一次這些 statements,結果才會永遠是 2,在以下這個情況下,XY.X.value 為 2 XY.Y.value 為1。
scala> :reset
Resetting interpreter state.
Forgetting this session history:
scala> object XY {
| object X {
| val value: Int = Y.value + 1
| }
| object Y {
| val value: Int = X.value + 1
| }
| }
defined object XY
scala> val randomnumber=math.random
randomnumber: Double = 0.7747791382247455
scala> println( if (randomnumber > 0.5) XY.X.value else XY.Y.value )
因為以上兩個狀況都有可能會發生,我們也可以知道,因為 XY.X.value 跟 XY.Y.value 兩個數值,其中一個是 1 另一個是 2,我們多呼叫幾次,就會發現結果也有可能會變成 1。
scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
2
scala> println(if (math.random > 0.5) XY.X.value else XY.Y.value)
1