scala語法問題: range的向上向下轉型?

(1 until N).map(x =&> (x until N).map(y =&> (x, y)))

以上這段代碼, 為什麼最終的結果是vector類型的二重數組?

這其中向上向下轉型的過程具體是怎樣的?


這是 CanBuildFrom 搞的鬼,map函數有個隱式參數,CanBuildFrom,可以參考以前的一篇blog:

map函數,隱式參數CanBuildFrom的細節 | 寫點什麼

可以結合這篇blog來分析你的這個例子。


謝邀,這個問題幾句話講不清楚,尤其對於初學者。不過我可以略微解答一下題主的疑惑。

首先,樓主的問題是為什麼代碼的結果類型是Vector, 而不是保持原來的Range類型。

這個問題很好回答:因為Range是一個特殊的數據結構,用於保存等間隔的一列連續的數組,在這個數據結構上調用map函數後,你還能保證結果一定還是一個等間隔的一列連續的數組嗎?顯然不能,那麼結果當然也不能再是Range類型了。

並且,就算結果還是「等差數列」,其類型也不會是Range了。為什麼呢?因為Scala編譯器不知道你代碼運行的會是什麼結果,它只能用一個更普適的類型來保存結果。比如下面的代碼:

Welcome to Scala 2.11.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_101).
Type in expressions for evaluation. Or try :help.

scala&> (1 until 6) map (5+)
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(6, 7, 8, 9, 10)

其次,Range上調用map之後得到的類型就一定是Vector類型嗎NO!

實際上,我可以讓Range調用map之後返回任意類型,lets coding:

scala&> import scala.collection.breakOut
import scala.collection.breakOut

//總是Vector沒意思,給我個List行不行?
scala&> val list: List[(Int, Int)] = (1 until 6).map(x =&> (x, x*x))(breakOut)
list: List[(Int, Int)] = List((1,1), (2,4), (3,9), (4,16), (5,25))

//List膩了,來個Set嘗一嘗。
scala&> val set: Set[(Int, Int)] = (1 until 6).map(x =&> (x, x*x))(breakOut)
set: Set[(Int, Int)] = Set((3,9), (1,1), (2,4), (5,25), (4,16))

//還是Map有意思。
scala&> val map: Map[Int, Int] = (1 until 6).map(x =&> (x, x*x))(breakOut)
map: Map[Int,Int] = Map(5 -&> 25, 1 -&> 1, 2 -&> 4, 3 -&> 9, 4 -&> 16)

看到了沒有?同樣的代碼,我希望結果是個List它就給我個List,我希望它是Map,我就得到了Map

強大吧?神奇吧?困惑吧?困惑就對了。Scala是一個神奇的語言,同樣也會讓初學者困惑,但一旦你真正了解它,就會知道它的強大。

最後,我們來揭開魔法的面紗,簡單解釋為什麼這個map函數如此牛B。

簡而言之,map函數的強大得益於Scala強大的類型系統與implicit機制。直接用代碼說話吧,下面是map的具體實現:

def map[B, That](f: A =&> B)
(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
for (x &<- this) b += f(x) b.result }

代碼並不複雜,要注意的有兩點:

1. map的返回值並非固定的,而是一個待定類型That。

2. map有一個implicit參數 CanBuildFrom[Repr, B, That],這個implicit參數使得map方法能夠根據運行時的上下文來選擇合適的返回類型

我估計大家還會有很多疑問,不過這個回答裡面就不一一解釋了,大家可以更深入的學習Scala的語法,或者去看看這個更全面的回答:Is the Scala 2.8 collections library a case of quot;the longest suicide note in historyquot;?


(1 until N).map(x =&> (x until N).map(y =&> (x, y)))

這個的推斷的類型應該是`IndexedSeq[IndexedSeq[(Int, Int)]]`,`IndexedSeq`是個`trait`,值的類型是`Vector[Vector[(Int, Int)]]`。

先給推薦了解的連接The Architecture of Scala Collections,這個是官方文檔。

mapCanBuildFrom的關係其他的答案都說了,簡單來說map來自TraversableLike這個trait,Range繼承了IndexedSeq。map被調用的時候尋找CanBuildFrom,而IndexedSeq中CanbuildFrom會使用同樣在IndexedSeq中伴生對象聲明的ReusableCBF返回的GenericCanBuildFrom中的newBuilder用的就是IndexedSeq.newBuilder即Vector.newBuilder[A]。

大致代碼是

class Range(val start: Int, val end: Int, val step: Int)
extends scala.collection.AbstractSeq[Int]
with IndexedSeq[Int]
with scala.collection.CustomParallelizable[Int, ParRange]
with Serializable {...}

對於IndexedSeq

object IndexedSeq extends IndexedSeqFactory[IndexedSeq] {
class Impl[A](buf: ArrayBuffer[A]) extends AbstractSeq[A]
with IndexedSeq[A]
with Serializable {
def length = buf.length
def apply(idx: Int) = buf.apply(idx)
}
def newBuilder[A]: Builder[A, IndexedSeq[A]] =
Vector.newBuilder[A]
// 給map用的canBuildFrom
implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, IndexedSeq[A]] =
ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]
}

然後ReusableCBF其實是collection中IndexedSeq對象

object IndexedSeq extends IndexedSeqFactory[IndexedSeq] {
// A single CBF which can be checked against to identify
// an indexed collection type.
override val ReusableCBF: GenericCanBuildFrom[Nothing] = new GenericCanBuildFrom[Nothing] {
override def apply() = newBuilder[Nothing]
}
def newBuilder[A]: Builder[A, IndexedSeq[A]] =
immutable.IndexedSeq.newBuilder[A]
implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, IndexedSeq[A]] =
ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]
}

這裡的immutable.IndexedSeq.newBuilder[A]就是上一段代碼裡面那個Vector.newBuilder[A]。

所以就得到了vector。

寫一行代碼,然後在Intellij裡面按照調用關係,點一點就能看到了。

最後,很巧的是,scala 2.13要改進這個。所以還推薦閱讀Scala 2.13 Collections Rework,改進之後可能就不會有這個疑惑了。


推薦閱讀:

在Haskell里,每個類型都可以構造出來一個此類型的表達式嗎?
React 0.14:揭秘局部組件狀態陷阱
Lens: 從入門到再次入門
Lazy computation 在實際應用中有什麼妙用?

TAG:Scala | 函數式編程 |