Scala-21 隱式轉換和隱式參數

隱式轉換

  • 隱式轉換函數( implicit conversion function )指的是那種以 implicit關鍵字聲明帶有單個參數函數。這樣的函數將被自動應用,將值從一種類型轉換爲另一種類型 。
  • 例如Fraction類,將整數n轉換爲Fraction(n,1),使用implicit def int2Fraction(n:Int)= Fraction(n,1)
//原有的Fraction定義
class Fraction (n:Int,d:Int){
  private var num = n
  private var den = d
  
  def this(x:Int,n:Int, d:Int){
    this(n,d)
    num = x * d + n
  }
  
  def *(other:Fraction):Fraction={
    new Fraction(num*other.num,den*other.den)
  }
  
  override def toString: String = s"${num}/${den}"
}

object Fraction{
  def apply(n: Int, d: Int): Fraction = new Fraction(n, d)
  def apply(x:Int,n: Int, d: Int): Fraction = new Fraction(x,n, d)
}

//隱式轉換函數
object Chapter21 extends App{
  implicit def int2Fraction(n:Int)= Fraction(n,1)
  println(4 * Fraction(3,4))
}
  • 隱式轉換函數可能會被顯示引入,函數的命名使用source2Target這種命名
  • 使用import scala.language.implicitConversions避免警告

利用隱式轉換豐富現有類庫的功能

  • 例如給File對象添加一個read方法
import java.io.File
import scala.io.Source
//定義一個豐富的類型。提供想要的方法
class RichFile(val from:File){
  def read = Source.fromFile(from.getPath).mkString
}
//隱式轉換函數,將原類型轉換爲富的類型
implicit def file2RichFile(from:File) = new RichFile(from)
//File對象可以使用read方法了
val a = new File("D:\\data\\shui.txt").read
println(a)
  • 除了提供一個轉換函數,還可以將RichFile聲明爲隱式類( implicit class ),隱式類必須有一個單入參的主構造器。 該構造器自動成爲那個隱式的轉換函數
import java.io.File
import scala.io.Source
//定義一個豐富的類型。提供想要的方法
implicit class RichFile(val from:File){
  def read = Source.fromFile(from.getPath).mkString
}

//File對象可以使用read方法了
val a = new File("D:\\data\\shui.txt").read
println(a)
  • 將這個經過豐富的類聲明爲值類( value class )就不會有RichFile被構造了
implicit class RichFile(val from:File) extends AnyVal{
  def read = Source.fromFile(from.getPath).mkString
}
  • 隱式類不能是頂層的類。你可以將它放在使用類型轉換的類中,或者另一個對象或類中,然後引入

引入隱式轉換

  • Scala會考慮如下的隱式轉換函數 :
  1. 位於源或目標類型的伴生對象中的隱式函數或隱式類
  2. 位於當前作用域中可以以單個標識符指代的隱式函數或隱式類。
  • 例如int2Fraction函數。 我們可以將它放到 Fraction伴生對象中,就可以直接使用。如果放在一個對象中例如Obj,需要導入Obj._,或Obj.int2Fraction,如果有包路徑則需要把包路徑也加上。排除這個隱式轉換import Obj.{int2Fraction => _ , _},跟包的引入一樣
  • 如果你想要搞清楚爲什麼編譯器沒有使用某個你認爲它應該使用的隱式轉換 ,可以試着將它顯式加上 ,例如調用 fraction2Double (3) * Fraction(4,5)。你可能就會得到顯示問題所在的錯誤提示了

隱式轉換規則

  • 兩個隱式轉換函數
implicit def int2Fraction(n:Int) = Fraction(n,1)
implicit def fraction2Double(f:Fraction):Double = f.num*1.0/f.den
  • 隱式轉換在如下三種各不相同的情況下會被考慮
  1. 當表達式的類型與預期的類型不同時:3 * Fraction(3,4)會得到結果2.25,調用 fraction2Double。Int類並沒有一個*(Fraction)方法,不過它有一個*(Double)方法。如果沒有fraction2Double會調用int2Fraction
  2. 當對象訪問一個不存在的成員時:將一個private設爲公開,調用3.num,3沒有num成員但是Fraction有
  3. 與對象調用某個方法,而該方法的參數聲明與傳人蔘數不匹配時:Fraction(3,4) * 3,由於*不接受Int,自動將Int轉爲Fraction
println(3 * Fraction(3,4))  //結果爲 2.25
println(Fraction(3,4) * 3)  //結果爲 9/4
  • 在以下三種情況下編譯器不會嘗試使用隱式轉換
  1. 如果代碼能夠在不使用隱式轉換的前提下通過編譯,則不會使用隱式轉換
  2. 編譯器不會嘗試同時執行多個轉換,比如 convert1(convert2(a)) * b
  3. 存在二義性的轉換是一個錯誤 。 舉例來說,如果 convert1(a) * b 和convert2 (a)* b都是合法的,編譯器將會報錯
  • 不屬於二義性的例子,3 * Fraction(4 , 5)既可以被轉換成3 * fraction2Double (Fraction (4, 5))也可以被轉換成int2Fraction(3) * Fraction(4, 5)第一個轉換將會勝出,因爲它無須改變被應用*方法的那個對象。
  • 如果你想要弄清楚編譯器使用了哪些時轉換,可以用如下的行參數來編譯自己的程序
    scalac -Xprint:typer MyProg.scala你將會看到加入隱式轉換後的源碼。
println(3 * Fraction(3,4))  和 println(Fraction(3,4) * 3)分別對應

scala.this.Predef.println(3.*(Fraction.fraction2Double(Fraction.apply(3, 4))));
scala.this.Predef.println(Fraction.apply(3, 4).*(Fraction.int2Fraction(3)));

隱式參數

  • 函數或方法可以帶有一個標記爲implicit的參數列表。在這種情況下,編譯器將會查找默認值,提供給本次函數調用
  • 注意這裏有兩個參數列表 。 這個函數是“柯里化的”
  case class Delimiters(left: String , right: String)
  def quoto(what:String)(implicit delims:Delimiters)={
    delims.left + what + delims.right
  }
  val s = quoto("Quick Scala")(Delimiters("<<",">>"))
  println(s)
  • 也可以略去隱式參數列表 :quote (”Bonjour le monde” )在這種情況下,編譯器將會查找一個類型爲Delimiters 的隱式值。 這必須是一個被聲明爲implicit的值。 編譯器將會在如下兩個地方查找一個這樣的對象:
  1. 在當前作用域所有可以用 單個標識符指代的滿足類型要求的val和def *
  2. 與所要求類型相關聯的類型的伴生對象 。 相關聯的類型包括所要求類型本身,以及它的類型參數 (如果它是一個參數化的類型的話)
object FrenchPunctuation{
  implicit val quoteDelimiters =Delimiters("<<",">>")
}
import FrenchPunctuation.quoteDelimiters
val s2 = quoto("Quick Scala")
  • 對於給定的數據類型,只能有一個隱式的值。因此,使用常用類型的隱式參數並不是一個好主意

利用隱式參數進行隱式轉換

  • 隱式參數也可以隱式轉換
  • 下例中,轉換函數order將T類型轉換爲Ordered[T],implicit order:T => Ordered[T]
def smaller[T](a:T,b:T)(implicit  order:T => Ordered[T]): T ={
  if (order(a)<b) a else b
}
  • 注意order是一個被打上了 implicit標籤的函數,並且在作用域內 。 因此,它不僅是一個隱式參數, 它還是一個隱式轉換 。 正因爲這樣,我們纔可以在雨數體中略去對order的顯式調用
def smaller2[T](a:T,b:T)(implicit  order:T => Ordered[T]): T ={
  if (a < b) a else b
}

上下文界定

  • 類型參數可以有一個形式爲T : M的上下文界定( context bound ),其中M是另一個泛型類型。 它要求作用域中存在一個類型爲M[T]的隱式值,下例中是Ordering[T]的隱式值
  • 如果我們new一個Pair (40,2),編譯器將推斷出我們需要一個Pair[Int]。 由於Ordering伴生對象中有一個類型爲 Ordering[Int]的隱式值 因此 Int滿足上下文界定。這個Ordering[Int] 就成爲該類的一個字段,其被傳入需要該值的方法當中
//寫法1
class Pair[T : Ordering](val first:T, val second:T){
  def smaller(implicit ord:Ordering[T]):T=
    if (ord.compare(first,second) < 0) first else second
}
  • Predef類的implicitly方法獲取隱式值def implicitly[T](implicit e: T) = e,implicitly方法用於從九幽之地獲取隱式值
//寫法2
class Pair2[T : Ordering](val first:T, val second:T){
  def smaller:T=
    if (implicitly[Ordering[T]].compare(first,second) < 0) first else second
}

//def implicitly[T](implicit e: T) = e
  • 或者可以利用Ordered特質中定義的從Ordering到Orde red的隱式轉換。一旦引入了這個轉換,你就可以使用關係操作符
//寫法3
class Pair3[T : Ordering](val first:T, val second:T){
  def smaller:T={
    import Ordered.orderingToOrdered
    if(first < second) first else second
  }
}
  • 可以隨時實例化Pair[T],只要滿足存在類型爲Ordering[T]的隱式值的條件即可

類型類

  • Scala標準類庫提供了很多有用的類型類,比如 Equiv 、 Ordering 、 Numeric 、Fractional 、 Hashing 、IsTraverableOnce 、 IsTraverableLike等 。 提供自定義的類型類也是很容易的 。
    關於類型類,最爲重要的一點是它們提供了一種“特設( ad hoc )”的多態機制 ,這跟繼承(譯者注 :即子類型多態)比起來,更爲寬鬆。
trait NumberLike[T]{
  def plus(x:T,y:T):T
  def divideBy(x:T,n:Int):T
}
object NumberLike{
  implicit object NumberLikeDouble extends NumberLike[Double]{
    def plus(x:Double,y:Double) = x + y
    def divideBy(x: Double, n: Int): Double = x / n
  }
  implicit object NumberLikeBigDecimal extends NumberLike[BigDecimal]{
    def plus(x:BigDecimal,y:BigDecimal):BigDecimal = x + y
    def divideBy(x: BigDecimal, n: Int): BigDecimal = x / n
  }
  def average[T](x:T,y:T)(implicit ev:NumberLike[T])=
    ev.divideBy(ev.plus(x,y),2)
  def average[T : NumberLike](x:T,y:T)={
    val ev = implicitly[NumberLike[T]]
    ev.divideBy(ev.plus(x,y),2)
  }
  class Point(val x:Double,val y: Double){

  }
  object Point{
    def apply(x:Double,y:Double)=new Point(x,y)
    implicit object NumberLikePoint extends NumberLike[Point]{
      def plus(p:Point,q:Point)=Point(p.x + q.x,p.y + q.y)
      def divideBy(p:Point,n:Int) = Point(p.x * 1.0 / n,p.y * 1.0 /n)
    }
  }
}

類型證明

import scala.annotation.implicitNotFound
@implicitNotFound (msg = " Cannot prove that ${From} <:< ${To}. ")
abstract class <:< [-From, +To] extends Function1[From,To]
object <:< {
  implicit def conforms[A]: A <:< A = new (A <:< A) {
    def apply(x: A):A= x
  }
}

def firstLast[A,C](it:C)(implicit ev:C <:< Iterable[A])=(it.head,it.last)

@implicitNotFound註解

  • 告訴編譯器在不能構造出帶有該註解的類型的參數時給出錯誤提示
    在這裏插入圖片描述

CanBuildFrom

發佈了75 篇原創文章 · 獲贊 83 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章