Scala的隱式轉(zhuǎn)換和參數(shù)

Key Words: Implicit conversions, Implicit parameters, Implicit class, Implicitly, Context bound

隱式轉(zhuǎn)換(Implicit conversions)

隱式定義就是允許編譯器插入一段程序來解決一些類型錯(cuò)誤(type errors)的手段

對(duì)于兩個(gè)軟件體亡脑,彼此設(shè)計(jì)時(shí)都沒有考慮到對(duì)方,此時(shí)隱式轉(zhuǎn)換通常比較有用邀跃。隱式轉(zhuǎn)換可以減少代碼中將一個(gè)類型轉(zhuǎn)換為另一個(gè)類型的顯示轉(zhuǎn)換(explicit conversions)

隱式規(guī)則

標(biāo)記規(guī)則(Marking rule)

只有標(biāo)記為implicit的定義才可用霉咨。可以用 implicit 來標(biāo)記任何變量拍屑、函數(shù)或?qū)ο蠖x

編譯器只會(huì)在顯示標(biāo)識(shí)為 implicit 的定義中進(jìn)行選擇

作用域規(guī)則(Scope rule)

被插入的隱式轉(zhuǎn)換必須是當(dāng)前作用域的單個(gè)標(biāo)識(shí)符(single identifier)途戒,或者跟隱式轉(zhuǎn)換的源類型或目標(biāo)類型有關(guān)聯(lián)

Scala 編譯器只會(huì)考慮那些在作用域內(nèi)的隱式轉(zhuǎn)換。因此僵驰,必須以某種方式將隱式轉(zhuǎn)換定義引入到當(dāng)前作用域才能使得它們可用

編譯器還會(huì)在隱式轉(zhuǎn)換的源類型或目標(biāo)類型的伴生對(duì)象中查找隱式定義

對(duì)于類庫而言喷斋,常見的做法是提供一個(gè)包含了一些有用的隱式轉(zhuǎn)換的 Preamble 對(duì)象,這樣使用這個(gè)類庫的代碼就可以通過一個(gè)“import Preamble._”來訪問該類庫的隱式轉(zhuǎn)換

作用域規(guī)則有助于模塊化推理(modular reasoning)矢渊。當(dāng)你閱讀一個(gè)代碼文件继准,你只需要考慮被引用的或者是使用全限定名的定義。這樣顯示寫代碼的好處對(duì)于對(duì)于隱式(implicits)也同樣的重要矮男。如果隱式在系統(tǒng)范圍(system-wide)內(nèi)有效移必,那么想理解一個(gè)源文件就不得不了解每個(gè)被引入的隱式

每次一個(gè)規(guī)則(One-at-a-time rule)

每次只能有一個(gè)隱式定義被插入

這個(gè)規(guī)則能夠減少有問題代碼的編譯時(shí)間,以及減少程序員編寫的和程序?qū)嶋H做的之間的差異

可以通過在隱式定義中使用隱式參數(shù)來繞過這個(gè)規(guī)則

// 下面的代碼只是為了說明繞過每次一個(gè)規(guī)則(One-at-a-time rule)毡鉴,沒有任何實(shí)際意義
implicit vall height = 1
case class Foo(width: Int, height: Int) {
  def bar = println(s"Foo: ${width} ${height}")
}

implicit def convert2Foo(width: Int)(implicit height: Int) = Foo(width, height)

1 bar

// 這樣就能讓編譯器插入兩個(gè)隱式定義了

顯示優(yōu)先原則(Explicits-first rule)

只要代碼按編寫的樣子能夠通過類型檢查崔泵,編譯器就不會(huì)嘗試隱式定義

其實(shí),到底留多少隱式轉(zhuǎn)換給編譯器來插入猪瞬,最終是代碼風(fēng)格的問題

隱式轉(zhuǎn)換命名

隱式轉(zhuǎn)換可以使用任意的名字憎瘸。只有在兩個(gè)地方對(duì)于命名需要進(jìn)行注意:

1.在方法的應(yīng)用中,顯示的寫出隱式轉(zhuǎn)換
2.決定哪些隱式可以在程序的哪些地方可用

隱式應(yīng)用場(chǎng)景

在 Scala 中隱式有三個(gè)應(yīng)用場(chǎng)景:類型的轉(zhuǎn)換(conversions to an expected type)陈瘦、接受者的轉(zhuǎn)換(conversions fo the receiver of a selection)以及隱式參數(shù)(implicit parameters)

類型的轉(zhuǎn)換

就是在上下文中使用一種類型幌甘,實(shí)際上期待另外一種不同的類型

不論何時(shí),編譯器看見一個(gè)類型 X痊项,但是實(shí)際需要的是 Y 類型锅风,編譯器就會(huì)尋找隱式函數(shù)來將 X 轉(zhuǎn)換為 Y

implicit def double2Int(d: Double) = d.toInt
val i: Int = 3.5

此處的類型轉(zhuǎn)換有一個(gè)原則:將更多約束的類型轉(zhuǎn)換為一個(gè)更為普通的類型

implicit def int2Double(i: Int) = i.toDouble
val d: Double = 1

接受者的轉(zhuǎn)換

可以通俗的理解,在一個(gè)對(duì)象上調(diào)用該對(duì)象并不存在的某個(gè)方法鞍泉,編譯器會(huì)幫你挑選合適的隱式轉(zhuǎn)換皱埠,來將此對(duì)象轉(zhuǎn)換為含有該方法的對(duì)象

case class Foo(f: Int)
case class Bar(b: Int) {
  def doSome = println(s"hello bar: $!")
}
implicit def foo2Bar(foo: Foo) = Bar(foo.f)

Foo(1) doSome

接受者的轉(zhuǎn)換主要有兩個(gè)用處:第一咖驮,可以平滑的集成一個(gè)新類到一個(gè)已有的類層次中边器;第二训枢,支持編寫 DSL(domain-specific language)

隱式類(Implicit class)

編譯器通過隱式類的類參數(shù)生成一個(gè)隱式轉(zhuǎn)換,該隱式將類參數(shù)轉(zhuǎn)換為該類本身

implicit class WaveArrow[A](x: A) {
  def ~> [B](y: B) = new Tuple2(x, y)
}

隱式類的定義有如下約束:
1.隱式類不能是一個(gè)樣例類(case class)忘巧,且構(gòu)造器只能含有一個(gè)形參
2.隱式類必須位于某個(gè)對(duì)象恒界、類或者特質(zhì)當(dāng)中

在實(shí)際應(yīng)用當(dāng)中,如果使用隱式類作為富包裝來給已有的類來添加一些方法砚嘴,上述的約束就無關(guān)緊要了

// 上面的隱式類定義仗处,編譯器會(huì)做如下等效的工作
implicit def WaveArrow[A](x: A) = new WaveArrow[A](x)

Map(1 ~> 2)

隱式參數(shù)

隱式參數(shù)給調(diào)用者提供更多被調(diào)用的函數(shù)的信息,通常用在泛型函數(shù)中

隱式參數(shù)用于最后一個(gè)柯里化后的參數(shù)列表枣宫,而不是最后一個(gè)參數(shù)

由于隱式參數(shù)的匹配是通過參數(shù)的類型來進(jìn)行的婆誓,所以要求隱式參數(shù)的類型盡可能的罕見與特別("rare" or "special"),以此來防止匹配的失誤

隱式參數(shù)還經(jīng)常被用來提供關(guān)于之前參數(shù)列表中顯示提到的類型的信息(Implicit parameters is that they are perhaps most often used to provide information about a type mentioned explicitly in an earlier parameter list)

通過下面例子來理解上述語句也颤,隱式參數(shù) ordering 的類型是 Ordering[T]洋幻,在此例中,該參數(shù)提供了更多關(guān)于類型 T 的信息翅娶,也就是如何對(duì) T 進(jìn)行排序文留。類型 T 在 List[T] 中被提及,也就是列表中元素的類型竭沫,而 List[T] 出現(xiàn)在 Ordering[T] 之前

def max[T](xs: List[T])(implicit ordering: Ordering[T]): T = {
  xs match {
    case List() => throw IllegalArgumentException
    case List(x) => x
    case x :: last => {
      val m = max(last)(ordering)
      if (ordering.gt(x, m)) x else m
    }
  }
}
隱式參數(shù)的風(fēng)格規(guī)則

1.使用定制化的命名類型燥翅,也就是說不要使用通用類型

// 使用定制化的命名類型
class Drink(s: String)
implicit val drink: Drink = new Drink("maotai")
def drinkSome(implicit drink: Drink) = {}

// 不要使用通用的類型
implicit val drink: String = "maotai"
def drinkSome(implicit drink: String) = {}

2.在隱式參數(shù)類型中至少使用一個(gè)能夠定義角色的名稱

def maxListPoorStyle[T](elems: List[T])(implicit orderer: (T, T) => Boolean): T

上述隱式參數(shù)的表達(dá)方式就會(huì)給讀者帶來困擾,也不能提供關(guān)于類型 T 的更多信息(比如函數(shù)式判斷相等蜕提、大于還是小于)

上下文邊界(Context bounds)

當(dāng)在一個(gè)函數(shù)的形參上面使用隱式森书,編譯器不僅試著用隱式值來補(bǔ)充該形參,而且在方法體中把該形參當(dāng)做可用的隱式

def max[T](xs: List[T])(implicit ordering: Ordering[T]): T = {
  xs match {
    case List() => throw IllegalArgumentException
    case List(x) => x
    case x :: last => {
      val m = max(last)                        // (ordering) is implicit
      if (ordering.gt(x, m)) x else m     // this ordering is still explicit
    }
  }
}

有種方式能夠減少代碼中對(duì)于 ordering 的使用

def implicitly[T](implicit t: T) = t  // 存在于標(biāo)準(zhǔn)庫中

def max[T](xs: List[T])(implicit ordering: Ordering[T]): T = {
  xs match {
    case List() => throw IllegalArgumentException
    case List(x) => x
    case x :: last => {
      val m = max(last)                                            // (ordering) is implicit
      if (implicitly[Ordering[T]].gt(x, m)) x else m     // this ordering is implicit now
    }
  }
}

// 這時(shí)你可以不用關(guān)注隱式參數(shù)具體的名字了
def max[T](xs: List[T])(implicit comparator: Ordering[T]): T = {
  xs match {
    case List() => throw IllegalArgumentException
    case List(x) => x
    case x :: last => {
      val m = max(last)                                            // (ordering) is implicit
      if (implicitly[Ordering[T]].gt(x, m)) x else m     // this ordering is implicit now
    }
  }
}

通過如上的表述谎势,隱式參數(shù)的形參名字并不重要凛膏。Scala 可以使用上下文邊界來省略隱式參數(shù)的定義精簡(jiǎn)方法頭

def max[T : Ordering](xs: List[T]): T = {
  xs match {
    case List() => throw IllegalArgumentException
    case List(x) => x
    case x :: last => {
      val m = max(last)                                            // (ordering) is implicit
      if (implicitly[Ordering[T]].gt(x, m)) x else m     // this ordering is implicit now
    }
  }
}

上下文邊界,可以把它理解為隱式參數(shù)使用的一個(gè)語法糖

隱式轉(zhuǎn)換適用情況

多轉(zhuǎn)換同時(shí)適用

在同一個(gè)作用域下脏榆,有多個(gè)轉(zhuǎn)換同時(shí)能夠工作猖毫,編譯器為了不制造困惑,會(huì)拒絕插入轉(zhuǎn)換

但是在 Scala 2.8 時(shí)放松了這個(gè)規(guī)則须喂。如果一個(gè)可用的轉(zhuǎn)換比另外一個(gè)轉(zhuǎn)換更加嚴(yán)格且具體吁断,編譯器會(huì)選擇更加具體的轉(zhuǎn)換

怎樣才能認(rèn)為更加具體呢?
1.前者參數(shù)的類型是后者的子類
2.如果轉(zhuǎn)換都是方法坞生,前者封裝的類擴(kuò)展子后者封裝的類

調(diào)試

scalac Xprint:typer xxx.scala

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仔役,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子恨胚,更是在濱河造成了極大的恐慌骂因,老刑警劉巖炎咖,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異序无,居然都是意外死亡均函,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門俄烁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人级野,你說我怎么就攤上這事页屠。” “怎么了蓖柔?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵辰企,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我况鸣,道長(zhǎng)牢贸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任镐捧,我火速辦了婚禮潜索,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懂酱。我一直安慰自己竹习,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布列牺。 她就那樣靜靜地躺著整陌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞎领。 梳的紋絲不亂的頭發(fā)上蔓榄,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音默刚,去河邊找鬼甥郑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛荤西,可吹牛的內(nèi)容都是我干的澜搅。 我是一名探鬼主播,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼邪锌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼勉躺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起觅丰,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤饵溅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后妇萄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜕企,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咬荷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轻掩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幸乒。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖唇牧,靈堂內(nèi)的尸體忽然破棺而出罕扎,到底是詐尸還是另有隱情,我是刑警寧澤丐重,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布腔召,位于F島的核電站,受9級(jí)特大地震影響扮惦,放射性物質(zhì)發(fā)生泄漏宴咧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一径缅、第九天 我趴在偏房一處隱蔽的房頂上張望掺栅。 院中可真熱鬧,春花似錦纳猪、人聲如沸氧卧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沙绝。三九已至,卻和暖如春鼠锈,著一層夾襖步出監(jiān)牢的瞬間闪檬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工购笆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粗悯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓同欠,卻偏偏與公主長(zhǎng)得像样傍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铺遂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容