簡體   English   中英

為什么我們需要Scala的CanBuildFrom中的From type參數

[英]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)),因此在沒有您使用的導入的情況下將所需的隱式帶入作用域。

這就是語法上的原因,但是有一個很好的語義原因使其成為理想的行為 - 除非指定了類型,否則當可能存在沖突時,編譯器無法解析正確的隱式。 例如,如果在構建器之間選擇StringList[Char] ,則編譯器需要選擇正確的"myFoo".map(_.toUpperCase)以啟用"myFoo".map(_.toUpperCase)返回String 如果每次隱式轉換都被納入范圍,那么這將是困難或不可能的。 上述規則旨在包含可能與當前范圍相關的有序列表,以防止出現此問題。

您可以在規范優秀答案中閱讀有關implicits和隱式范圍的更多信息。

這就是為什么你的另外兩個解決方案的工作:當你明確指定調用的返回類型map (在沒有該版本的Source參數),然后鍵入推斷進場,編譯器可以推斷感興趣的參數ThatContainer ,並且因此隱含的范圍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 

我們將在FactoryReprBThat的伴隨對象中定義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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM