2012-05-26 55 views
6

我使用日階2.10.0快照(20120522),並具有以下的Scala文件:scala 2.10.x.中的隱式解析這是怎麼回事?

這一個限定的類型類和基本類型類實例:

package com.netgents.typeclass.hole 

case class Rabbit 

trait Hole[A] { 
    def findHole(x: A): String 
} 

object Hole { 
    def apply[A: Hole] = implicitly[Hole[A]] 
    implicit val rabbitHoleInHole = new Hole[Rabbit] { 
    def findHole(x: Rabbit) = "Rabbit found the hole in Hole companion object" 
    } 
} 

這是包對象:

package com.netgents.typeclass 

package object hole { 

    def findHole[A: Hole](x: A) = Hole[A].findHole(x) 

    implicit val rabbitHoleInHolePackage = new Hole[Rabbit] { 
    def findHole(x: Rabbit) = "Rabbit found the hole in Hole package object" 
    } 
} 

這裏是測試:

package com.netgents.typeclass.hole 

object Test extends App { 

    implicit val rabbitHoleInOuterTest = new Hole[Rabbit] { 
    def findHole(x: Rabbit) = "Rabbit found the hole in outer Test object" 
    } 

    { 
    implicit val rabbitHoleInInnerTest = new Hole[Rabbit] { 
     def findHole(x: Rabbit) = "Rabbit found the hole in inner Test object" 
    } 

    println(findHole(Rabbit())) 
    } 
} 

正如你所看到的,Hole是一個簡單的類型類,它定義了一個Rabbit試圖找到的方法。我正試圖弄清楚它的隱式解決規則。

  • 與未加註釋的所有四個類型類的實例,scalac抱怨上rabbitHoleInHolePackagerabbitHoleInHole歧義。 (爲什麼?)

  • 如果我註釋掉rabbitHoleInHole,scalac編譯和我回來「兔子發現孔封裝對象中的洞」。 (不應在局部範圍implicits優先?)

  • ,如果我再註釋掉rabbitHoleInHolePackage,scalac抱怨上rabbitHoleInOuterTestrabbitHoleInInnerTest歧義。 (爲什麼?在文章eed3si9n,下面列出的URL,他發現implicits BTW內部和外部的範圍可以採取不同的優先級。)

  • ,如果我再註釋掉rabbitHoleInInnerTest,scalac編譯和我回來「兔子發現洞中在外部測試對象「中。

正如你所看到的,上述行爲並不遵循我在隱式解決方案上閱讀過的規則。我只描述了一小部分組合可以用來評論/取消註釋實例,其中大部分實際上都很奇怪 - 而且我還沒有涉及到進口和子類。

我已閱讀並觀看了presentation by suereth,stackoverflow answer by sobrala very elaborate revisit by eed3si9n,但我仍然完全困惑不解。

+1

你用2.9.x試過嗎? –

+0

的行爲是不是在2.9.2 *與所有四個類型類實例註釋掉,scalac編譯不同,我回來「兔發現,在內部測試對象中的洞」 *如果我註釋掉rabbitHoleInHole,scalac編譯和我得到「兔子在內部測試對象中找到了洞」。 *如果我然後註釋掉rabbitHoleInHolePackage,scalac編譯,我回來了「兔子發現內部測試對象的洞」。 *如果我然後註釋掉rabbitHoleInInnerTest,scalac編譯並且我回來了「兔子在外部測試對象中找到了洞」。 –

+0

斯卡拉的推理規則中有一個錯誤,達到了2.9,實際上Stack Overflow中的某個人發現了這個錯誤,他試圖理解規範是如何規定他所看到的行爲的。 –

回答

4

讓我們開始在包對象和類型類的同伴禁用implicits:

package rabbit { 
    trait TC 

    object Test extends App { 
    implicit object testInstance1 extends TC { override def toString = "test1" } 

    { 
     implicit object testInstance2 extends TC { override def toString = "test2" } 

     println(implicitly[TC]) 
    } 
    } 
} 

Scalac查找任何範圍implicits,發現testInstance1testInstance2。事實上,如果一個人的名字相同,則只能使用正常的陰影規則。我們已經選擇了不同的名稱,並且沒有隱含的比另一個更具體,所以歧義被正確報告。我們試試另一個例子,這次我們將在本地範圍內對包對象中的一個隱式地執行隱式操作。

package rabbit { 
    object `package` { 
    implicit object packageInstance extends TC { override def toString = "package" } 
    } 

    trait TC 

    object Test extends App { 
    { 
     implicit object testInstance2 extends TC { override def toString = "test2" } 

     println(implicitly[TC]) 
    } 
    } 
} 

這裏會發生什麼?與前面一樣,隱式搜索的第一階段考慮了調用站點的所有含義。在這種情況下,我們有testInstance2packageInstance。這些是不明確的,但在報告錯誤之前,第二階段開始,並搜索隱含範圍TC

但這裏隱含的範圍是什麼? TC甚至沒有伴侶對象?我們需要回顧Scala參考文獻7.2中的精確定義。

類型T的隱式範圍由所有同伴模塊 (第5.4節)的與該隱式參數的 類型相關聯的類。在這裏,我們說一個C類與類型T相關聯的,如果是 是一個基類(§5.1.2)T.

類型T的部件的某些部分的有:

  • 如果T是複合型T1 with ... with Tn, 所述的T1, ..., Tn部件的結合,以及T本身,
  • 如果T是一個參數類型S[T1, ..., Tn],的S和 的部分的聯合,
  • 如果T是一個單型p.type,的p類型的部件,
  • 如果T是一種類型的投影S#U,的S以及T本身的部件,在所有其他情況下
  • ,只是T本身。

我們正在尋找rabbit.TC。從類型系統的角度來看,這是一個簡寫:rabbit.type#TC,其中rabbit.type是表示包的類型,就好像它是一個常規對象。調用規則4給了我們零件TCp.type

那麼,這一切意味着什麼?簡單來說,包對象中的隱式成員也是隱式作用域的一部分!

在上面的例子中,這給了我們在隱式搜索的第二階段的明確選擇。

其他例子可以用相同的方式解釋。

總結:在兩個階段

  • 隱搜索前進。導入和陰影的通常規則決定候選人列表。
  • 封閉包對象中的隱式成員也可能在範圍內,假設您使用的是nested packages
  • 如果有多個候選人,則使用靜態超載規則來查看是否有勝者。作爲一個破解者,編譯器比第一個超類中定義的另一個隱含更優先。
  • 如果第一階段失敗,則以相同的方式查閱隱式範圍。 (區別在於來自不同同伴的隱式成員可能具有相同的名稱而不會互相影響。)
  • 來自封裝包的包對象中的含義也是此隱式作用域的一部分。

UPDATE

在斯卡拉2.9.2,該行爲是不同的,錯誤的。

package rabbit { 
    trait TC 

    object Test extends App { 
    implicit object testInstance1 extends TC { override def toString = "test1" } 

    { 
     implicit object testInstance2 extends TC { override def toString = "test2" } 

     // wrongly considered non-ambiguous in 2.9.2. The sub-class rule 
     // incorrectly considers: 
     // 
     // isProperSubClassOrObject(value <local Test>, object Test) 
     // isProperSubClassOrObject(value <local Test>, {object Test}.linkedClassOfClass) 
     // isProperSubClassOrObject(value <local Test>, <none>) 
     //  (value <local Test>) isSubClass <none> 
     //  <notype> baseTypeIndex <none> >= 0 
     //  0 >= 0 
     //  true 
     //  true 
     // true 
     // true 
     // 
     // 2.10.x correctly reports the ambiguity, since the fix for 
     // 
     // https://issues.scala-lang.org/browse/SI-5354?focusedCommentId=57914#comment-57914 
     // https://github.com/scala/scala/commit/6975b4888d 
     // 
     println(implicitly[TC]) 
    } 
    } 
} 
+0

順便說一下,'scalac -Yinfer-debug'可以幫助候選人和選定的隱含。 – retronym

+0

是的,我可以在2.10.x中重複相同的行爲,並且您對隱式分辨率的解釋非常清晰。然而,我使用2.9.2觀察到的行爲卻完全不同。在第一種情況下,您禁用了包對象和類型類伴侶對象中的兩個含義,編譯時沒有任何含糊不清警告的代碼在運行時返回「test2」。在第二種情況下,啓用了包對象中的隱含功能,則按照陳述編譯但「test2」的代碼已打印出來而不是「包」。在2.10.x中有隱式分辨率的改變嗎? –

+0

看起來像['isProperSubClassOrObject']中的細微錯誤(https://github.com/scala/scala/blob/v2.9.2/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L984) - - 它沒有說明'sym2'可能是一個本地塊,因爲它是伴侶,所以它有''。我還沒有想出在2.10.x中改變了什麼以避免這種情況。 – retronym