對Scala新手的一個隱含問題彷佛是:編譯器在哪裏尋找隱含? 個人意思是隱含的,由於這個問題彷佛永遠不會徹底造成,好像沒有它的話。 :-)例如,下面integral
的值來自何處? app
scala> import scala.math._ import scala.math._ scala> def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)} foo: [T](t: T)(implicit integral: scala.math.Integral[T])Unit scala> foo(0) scala.math.Numeric$IntIsIntegral$@3dbea611 scala> foo(0L) scala.math.Numeric$LongIsIntegral$@48c610af
對於那些決定學習第一個問題的答案的人來講,另外一個問題是,在某些明顯模糊的狀況下(但不管如何編譯),編譯器如何選擇使用哪一個隱式? 函數
例如, scala.Predef
定義了兩個來自String
轉換:一個轉換爲WrappedString
,另外一個轉換爲StringOps
。 可是,這兩個類都有不少方法,因此爲何Scala不會抱怨模糊,好比說調用map
? 學習
注意:這個問題的靈感來自另外一個問題 ,但願以更通常的方式陳述問題。 該示例是從那裏複製的,由於它在答案中被引用。 ui
Scala中的Implicits指的是能夠「自動」傳遞的值,能夠這麼說,或者是自動轉換爲從一種類型到另外一種類型的轉換。 this
說到很是簡單說一下後一種類型,若是調用一個方法m
的物體上o
一類C
,那類不支持的方法m
,而後斯卡拉將尋求從隱式轉換C
的東西, 不支持m
。 一個簡單的例子是String
上的方法map
: spa
"abc".map(_.toInt)
String
不支持方法map
,但StringOps
支持,而且存在從String
到StringOps
的隱式轉換(請參閱implicit def augmentString
上的implicit def augmentString
Predef
)。 scala
另外一種隱含的是隱式參數 。 它們像任何其餘參數同樣傳遞給方法調用,但編譯器會嘗試自動填充它們。 若是不能,它會抱怨。 能夠明確地傳遞這些參數,例如,人們如何使用breakOut
(請參閱有關breakOut
問題,在您感到挑戰的那一天)。 翻譯
在這種狀況下,必須聲明須要隱式,例如foo
方法聲明: 3d
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
有一種狀況,隱式是隱式轉換和隱式參數。 例如: code
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value) getIndex("abc", 'a')
方法getIndex
能夠接收任何對象,只要從其類可用於Seq[T]
的隱式轉換。 所以,我能夠將一個String
傳遞給getIndex
,它會起做用。
在幕後,編譯器將seq.IndexOf(value)
更改成conv(seq).indexOf(value)
。
這很是有用,有寫句法糖。 使用這個語法糖, getIndex
能夠像這樣定義:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
該語法糖被描述爲視界 ,相似於上限 ( CC <: Seq[Int]
)或下限 ( T >: Null
)。
隱式參數中的另外一種常見模式是類型類模式 。 此模式容許爲未聲明它們的類提供公共接口。 它既能夠做爲橋樑模式 - 得到關注點的分離 - 也能夠做爲適配器模式。
您提到的Integral
類是類型類模式的典型示例。 另外一個關於Scala標準庫的例子是Ordering
。 有一個庫大量使用這種模式,稱爲Scalaz。
這是它的一個使用示例:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = { import integral._ // get the implicits in question into scope list.foldLeft(integral.zero)(_ + _) }
它也有語法糖,稱爲上下文綁定 ,因爲須要引用隱式,所以它不太有用。 該方法的直接轉換以下所示:
def sum[T : Integral](list: List[T]): T = { val integral = implicitly[Integral[T]] import integral._ // get the implicits in question into scope list.foldLeft(integral.zero)(_ + _) }
當您只需將它們傳遞給使用它們的其餘方法時,上下文邊界會更有用。 例如,在Seq
sorted
的方法須要隱式Ordering
。 要建立方法reverseSort
,能夠編寫:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
由於Ordering[T]
被隱式傳遞給reverseSort
,因此它能夠隱式地將它傳遞給sorted
。
當編譯器看到須要隱式時,或者由於你正在調用一個在對象類上不存在的方法,或者由於你正在調用一個須要隱式參數的方法,它會搜索一個適合須要的隱式。
此搜索遵循某些規則,這些規則定義哪些隱含可見,哪些不可見。 下表顯示了編譯器搜索implicits的位置,取自Josh Suereth關於implicits的精彩演示 ,我衷心向全部想要提升Scala知識的人推薦。 從那時起,它就獲得了補充和反饋。
下面的數字1下可用的含義優先於數字2下的含義。除此以外,若是有幾個符合條件的參數與隱式參數的類型匹配,則將使用靜態重載分辨率的規則選擇最具體的參數(請參閱Scala)規範§6.26.3)。 更詳細的信息能夠在我在本答案末尾連接的問題中找到。
讓咱們舉一些例子:
implicit val n: Int = 5 def add(x: Int)(implicit y: Int) = x + y add(5) // takes n from the current scope
import scala.collection.JavaConversions.mapAsScalaMap def env = System.getenv() // Java map val term = env("TERM") // implicit conversion from Java Map to Scala Map
def sum[T : Integral](list: List[T]): T = { val integral = implicitly[Integral[T]] import integral._ // get the implicits in question into scope list.foldLeft(integral.zero)(_ + _) }
編輯 :彷佛這沒有不一樣的優先權。 若是您有一些示例代表優先級區別,請發表評論。 不然,不要依賴這個。
這與第一個示例相似,但假設隱式定義與其使用位於不一樣的文件中。 另請參閱如何使用包對象來引入含義。
這裏有兩個對象伴侶。 首先,查看「源」類型的對象伴隨。 例如,在對象Option
有一個隱式轉換爲Iterable
,所以能夠在Option
上調用Iterable
方法,或者將Option
傳遞給指望Iterable
東西。 例如:
for { x <- List(1, 2, 3) y <- Some('x') } yield (x, y)
該表達式由編譯器翻譯爲
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
可是, List.flatMap
須要TraversableOnce
,而Option
不是。 而後編譯器查看Option
的對象夥伴,並找到轉換爲Iterable
,這是一個TraversableOnce
,使這個表達式正確。
第二,預期類型的伴隨對象:
List(1, 2, 3).sorted
sorted
的方法採用隱式Ordering
。 在這種狀況下,它查看對象Ordering
,與Ordering
類的伴隨,並在那裏找到隱式的Ordering[Int]
。
請注意,還會查看超類的伴隨對象。 例如:
class A(val n: Int) object A { implicit def str(a: A) = "A: %d" format a.n } class B(val x: Int, y: Int) extends A(y) val b = new B(5, 2) val s: String = b // s == "A: 2"
這就是Scala在你的問題中找到隱式Numeric[Int]
和Numeric[Long]
的方式,順便說一句,由於它們是在Numeric
中找到的,而不是Integral
。
若是您的方法具備參數類型A
,那麼也將考慮類型A
的隱式範圍。 「隱式範圍」是指全部這些規則將以遞歸方式應用 - 例如,根據上述規則,將搜索A
的伴隨對象的含義。
請注意,這並不意味着將搜索A
的隱式範圍以查找該參數的轉換,而不是整個表達式的轉換。 例如:
class A(val n: Int) { def +(other: A) = new A(n + other.n) } object A { implicit def fromInt(n: Int) = new A(n) } // This becomes possible: 1 + new A(1) // because it is converted into this: A.fromInt(1) + new A(1)
這是自Scala 2.9.1以來可用的。
這是使類型類模式真正起做用所必需的。 例如,考慮Ordering
:它在其伴隨對象中帶有一些含義,但您沒法向其中添加內容。 那麼如何爲本身的類自動找到Ordering
?
讓咱們從實現開始:
class A(val n: Int) object A { implicit val ord = new Ordering[A] { def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n) } }
因此,考慮一下你打電話時會發生什麼
List(new A(5), new A(2)).sorted
正如咱們所看到的, sorted
的方法須要一個Ordering[A]
(實際上,它須要一個Ordering[B]
,其中B >: A
)。 在Ordering
沒有任何這樣的東西,而且沒有「源」類型可供查看。 顯然,它是在A
找到它,這是Ordering
的類型參數 。
這也是各類收集方法指望CanBuildFrom
工做的方式:在伴隨對象中找到CanBuildFrom
類型參數的CanBuildFrom
。
注意 : Ordering
定義爲trait Ordering[T]
,其中T
是類型參數。 之前,我說Scala查看了內部類型參數,這沒有多大意義。 上面隱含的是Ordering[A]
,其中A
是實際類型,而不是類型參數:它是Ordering
的類型參數 。 請參閱Scala規範的第7.2節。
這是從Scala 2.8.0開始提供的。
我實際上沒有看過這個例子。 若是有人能夠分享,我將不勝感激。 原理很簡單:
class A(val n: Int) { class B(val m: Int) { require(m < n) } } object A { implicit def bToString(b: A#B) = "B: %d" format b.m } val a = new A(5) val b = new a.B(3) val s: String = b // s == "B: 3"
我很肯定這是一個玩笑,但這個答案可能不是最新的。 所以,不要將此問題視爲正在發生的事情的最終仲裁者,若是您確實注意到它已通過時,請通知我,以便我能夠解決它。
編輯
相關問題:
我想找出隱式參數解析的優先級,而不只僅是它尋找的位置,因此我寫了一篇博客文章, 從新審視了沒有進口稅的 隱含 (並在一些反饋後再次隱含參數優先 )。
這是清單:
若是在任一階段咱們發現多個隱式,則使用靜態重載規則來解決它。