Scala在哪裏尋找暗示?

對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


#1樓

含義的類型

Scala中的Implicits指的是能夠「自動」傳遞的值,能夠這麼說,或者是自動轉換爲從一種類型到另外一種類型的轉換。 this

隱式轉換

說到很是簡單說一下後一種類型,若是調用一個方法m的物體上o一類C ,那類不支持的方法m ,而後斯卡拉將尋求從隱式轉換C的東西, 支持m 。 一個簡單的例子是String上的方法mapspa

"abc".map(_.toInt)

String不支持方法map ,但StringOps支持,而且存在從StringStringOps的隱式轉換(請參閱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來自哪裏?

當編譯器看到須要隱式時,或者由於你正在調用一個在對象類上不存在的方法,或者由於你正在調用一個須要隱式參數的方法,它會搜索一個適合須要的隱式。

此搜索遵循某些規則,這些規則定義哪些隱含可見,哪些不可見。 下表顯示了編譯器搜索implicits的位置,取自Josh Suereth關於implicits的精彩演示 ,我衷心向全部想要提升Scala知識的人推薦。 從那時起,它就獲得了補充和反饋。

下面的數字1下可用的含義優先於數字2下的含義。除此以外,若是有幾個符合條件的參數與隱式參數的類型匹配,則將使用靜態重載分辨率的規則選擇最具體的參數(請參閱Scala)規範§6.26.3)。 更詳細的信息能夠在我在本答案末尾連接的問題中找到。

  1. 首先看當前範圍
    • 當前範圍中定義的隱含
    • 明確的進口
    • 通配符導入
    • 其餘文件中的範圍相同
  2. 如今看看相關的類型
    • 一種類型的伴隨對象
    • 參數類型的隱含範圍(2.9.1)
    • 類型參數的隱含範圍(2.8.0)
    • 嵌套類型的外部對象
    • 其餘方面

讓咱們舉一些例子:

當前範圍中定義的含義

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"

其餘尺寸

我很肯定這是一個玩笑,但這個答案可能不是最新的。 所以,不要將此問題視爲正在發生的事情的最終仲裁者,若是您確實注意到它已通過時,請通知我,以便我能夠解決它。

編輯

相關問題:


#2樓

我想找出隱式參數解析的優先級,而不只僅是它尋找的位置,因此我寫了一篇博客文章, 從新審視了沒有進口稅的 隱含 (並在一些反饋後再次隱含參數優先 )。

這是清單:

  • 1)經過本地聲明,導入,外部做用域,繼承,無前綴可訪問的包對象,對當前調用做用域可見。
  • 2) 隱式做用域 ,它包含全部類型的伴隨對象和包對象,它們與咱們搜索的隱式類型有一些關係(即類型的包對象,類型自己的伴隨對象,其類型構造函數,若是有的話)它的參數,若是有的話,還有它的超類型和超級特徵)。

若是在任一階段咱們發現多個隱式,則使用靜態重載規則來解決它。

相關文章
相關標籤/搜索