![](/img/trans.png)
[英]Scala: Why do we need to keep type members in the package object?
[英]Why do we need the From type parameter in Scala's CanBuildFrom
我正在嘗試一組自定義容器函數,並從Scala的集合庫中獲取有關CanBuildFrom[-From, -Elem, -To]
隱式參數的CanBuildFrom[-From, -Elem, -To]
。
在Scala的集合中, CanBuildFrom
可以對map
等函數的返回類型進行參數化。 在內部, CanBuildFrom
參數像工廠一樣使用: map
在其輸入元素map
應用它的第一個順序函數,將每個應用程序的結果提供給CanBuildFrom
參數,最后調用CanBuildFrom的result
方法,該方法構建map
調用的最終結果。
在我的理解-From
類型參數CanBuildFrom[-From, -Elem, -To]
僅用於在apply(from: From): Builder[Elem, To]
它創建了一個Builder
的一些元件預先初始化。 在我的自定義容器函數中,我沒有那個用例,所以我創建了自己的CanBuildFrom
變量Factory[-Elem, +Target]
。
現在我可以有一個特性Mappable
trait Factory[-Elem, +Target] {
def apply(elems: Traversable[Elem]): Target
def apply(elem: Elem): Target = apply(Traversable(elem))
}
trait Mappable[A, Repr] {
def traversable: Traversable[A]
def map[B, That](f: A => B)(implicit factory: Factory[B, That]): That =
factory(traversable.map(f))
}
和一個實現Container
object Container {
implicit def elementFactory[A] = new Factory[A, Container[A]] {
def apply(elems: Traversable[A]) = new Container(elems.toSeq)
}
}
class Container[A](val elements: Seq[A]) extends Mappable[A, Container[A]] {
def traversable = elements
}
不幸的是,打電話給地圖
object TestApp extends App {
val c = new Container(Seq(1, 2, 3, 4, 5))
val mapped = c.map { x => 2 * x }
}
產生錯誤消息not enough arguments for method map: (implicit factory: tests.Factory[Int,That])That. Unspecified value parameter factory
not enough arguments for method map: (implicit factory: tests.Factory[Int,That])That. Unspecified value parameter factory
。 當我添加顯式導入import Container._
或當我顯式指定預期的返回類型val mapped: Container[Int] = c.map { x => 2 * x }
時,錯誤就消失了val mapped: Container[Int] = c.map { x => 2 * x }
當我向Factory
添加一個未使用的第三個類型參數時,所有這些“變通方法”變得不必要了
trait Factory[-Source, -Elem, +Target] {
def apply(elems: Traversable[Elem]): Target
def apply(elem: Elem): Target = apply(Traversable(elem))
}
trait Mappable[A, Repr] {
def traversable: Traversable[A]
def map[B, That](f: A => B)(implicit factory: Factory[Repr, B, That]): That =
factory(traversable.map(f))
}
並更改Container
的隱式定義
object Container {
implicit def elementFactory[A, B] = new Factory[Container[A], B, Container[B]] {
def apply(elems: Traversable[A]) = new Container(elems.toSeq)
}
}
最后我的問題是:為什么看似未使用的-Source
類型參數需要解析隱式?
並且作為一個額外的元問題:如果您沒有工作實現(集合庫)作為模板,您如何解決這些問題?
澄清
解釋為什么我認為隱式解決方案應該在沒有附加-Source
類型參數的情況下工作可能會有所幫助。
根據關於隱式解析的文檔條目 ,在類型的伴隨對象中查找含義。 作者沒有提供來自伴隨對象的隱式參數的示例(他只解釋了隱式轉換),但我認為這意味着對Container[A]#map
應該使object Container
所有隱含可用作隱式參數,包括我的elementFactory
。 這個假設得到以下事實的支持:它足以提供目標類型(沒有額外的顯式導入!!)來獲得隱式解析。
額外的類型參數根據隱式解析的規范啟用此行為。 以下是常見問題答案的摘要, 其中涉及的內容來自 (相關部分加粗):
首先看當前范圍:
- 當前范圍中定義的隱含
- 明確的進口
- 通配符導入
現在看看相關的類型:
- 類型的伴隨對象(1)
- 參數類型的隱含范圍(2)
- 類型參數的隱含范圍(3)
- 嵌套類型的外部對象
- 其他方面
當您在Mappable
上調用map
時,可以通過(2)從其參數的隱式范圍中獲得隱含。 由於在這種情況下它的參數是Factory
,這意味着你也可以從(3)的Factory
的所有類型參數的隱式范圍中得到隱含。 接下來是關鍵:只有當您將Source
作為類型參數添加到Factory
,才會獲得Container
隱式范圍內的所有Container
。 由於Container
的隱式作用域包含其伴隨對象中定義的含義(通過(1)),因此在沒有您使用的導入的情況下將所需的隱式帶入作用域。
這就是語法上的原因,但是有一個很好的語義原因使其成為理想的行為 - 除非指定了類型,否則當可能存在沖突時,編譯器無法解析正確的隱式。 例如,如果在構建器之間選擇String
或List[Char]
,則編譯器需要選擇正確的"myFoo".map(_.toUpperCase)
以啟用"myFoo".map(_.toUpperCase)
返回String
。 如果每次隱式轉換都被納入范圍,那么這將是困難或不可能的。 上述規則旨在包含可能與當前范圍相關的有序列表,以防止出現此問題。
您可以在規范或優秀答案中閱讀有關implicits和隱式范圍的更多信息。
這就是為什么你的另外兩個解決方案的工作:當你明確指定調用的返回類型map
(在沒有該版本的Source
參數),然后鍵入推斷進場,編譯器可以推斷感興趣的參數That
是Container
,並且因此隱含的范圍Container
進入活動范圍,包括其同伴對象implicits。 當您使用顯式導入時,所需的隱含在范圍內,非常簡單。
至於你的meta-question,你總是可以點擊源代碼(或者只是檢查repo),構建最小的例子(就像你有的那樣),並在這里提問。 使用REPL可以幫助處理這樣的小例子。
該規范有一個關於隱式參數解析的專用部分 。 根據該部分,隱含參數的參數有兩個來源。
首先,符合條件的是所有標識符x,它們可以在沒有前綴的方法調用點訪問,並且表示隱式定義或隱式參數。 因此,符合條件的標識符可以是本地名稱,也可以是封閉模板的成員,或者可以通過import子句在沒有前綴的情況下訪問它。
因此,每個沒有限定條件且可以使用implicit
關鍵字標記的名稱都可以用作隱式參數。
第二個符合條件的也是屬於隱式參數類型T的隱式作用域的某個對象的所有隱式成員。類型T的隱式作用域包含與隱式參數類型相關聯的所有類的伴隨模塊。 在這里,我們說如果類C是T的某個部分的基類,則它與類型T相關聯。
隱式參數列表類型的所有部分的伴隨對象(也稱為伴隨模塊)中定義的隱含也可用作參數。 所以在原始的例子中
def map[B, That](f: A => B)(implicit factory: Factory[Repr, B, That]): That
我們將在Factory
, Repr
, B
和That
的伴隨對象中定義implicits。 正如Ben Reich在他的回答中所指出的,這解釋了為什么Repr
類型參數是查找隱式參數所必需的。
這就是它使用顯式定義的返回類型的原因
val mapped: Container[Int] = c.map { x => 2 * x }
使用顯式返回類型定義,類型推斷為Factory[Repr, B, That]
的That
參數選擇Container[Int]
Factory[Repr, B, That]
。 因此,也可以使用Container
中定義的Container
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.