Scala Essentials: 隱式轉換

horance發表於2019-02-16

Class Extension

"+9519760513".exists(_.isDigit)

java.lang.String並存在exists方法,為此標準庫中在Predef定義了一個隱式轉換,使String隱式地轉換為StringOps,從而提供更多地操作字串的方法。

object Predef {
  implicit def augmentString(x: String): StringOps = new StringOps(x)
}

Implicit Resolution Rules

Marking Rule

Only definitions marked implicit are available.

object Predef {
  implicit def intWrapper(x: Int) = new scala.runtime.RichInt(x)
}
object Predef {
  implicit final class any2stringadd[A](private val self: A) extends AnyVal {
    def +(other: String): String = String.valueOf(self) + other
  }
}

Scope Rule

An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion.

case class Yard(val amount: Int)
case class Mile(val amount: Int)

mile2yard可以定義在object Mile

object Mile {
  implicit def mile2yard(mile: Mile) = new Yard(10*mile.amount)
}

也可以定義在object Yard

object Yard {
  implicit def mile2yard(mile: Mile) = new Yard(10*mile.amount)
}

轉換為目標型別時,常常發生如下兩個場景:

  • 傳遞引數時,但型別匹配失敗;

def accept(yard: Yard) = println(yard.amount + " yards")
accept(Mile(10))
  • 賦值表示式,但型別匹配失敗

val yard: Yard = Mile(10)

Other Rules

  • One-at-a-time Rule: Only one implicit is tried.

  • Explicits-First Rule: Whenever code type checks as it is written, no implicits are attempted.

  • No-Ambiguty Rule: An implicit conversion is only inserted if there is no other possible conversion is inserted.

Where implicits are tried?

  • Conversions to an expected type

    • 傳遞引數時,但型別匹配失敗;

    • 賦值表示式,但型別匹配失敗

  • Conversions of the receiver of a selection

    • 呼叫方法,方法不存在

    • 呼叫方法,方法存在,但引數型別匹配失敗

  • Implicit parameters

Implicit parameters

import scala.math.Ordering

case class Pair[T](first: T, second: T){
  def smaller(implicit order: Ordering[T]) =
    order.min(first, second)
}

TInt

Pair(1, 2).smaller

編譯器實際呼叫:

Pair(1, 2).smaller(Ordering.Int)

其中Ordering.Int定義在Ordering的伴生物件中

object Ordering {
  trait IntOrdering extends Ordering[Int] {
    def compare(x: Int, y: Int) =
      if (x < y) -1
      else if (x == y) 0
      else 1
  }
  implicit object Int extends IntOrdering
}

也就是說

implicitly[Ordering[Int]] == Ordering.Int  // true

其中,implicitly為定義在Predef的一個工具函式,用於提取隱式值

@inline def implicitly[T](implicit e: T) = e

T為自定義型別

import scala.math.Ordering

case class Point(x: Int, y: Int)

object Point {
  implicit object OrderingPoint extends Ordering[Point] {
    def compare(lhs: Point, rhs: Point): Int =
      (lhs.x + lhs.y) - (rhs.x + rhs.y)
  }
}
Pair(Point(0, 0), Point(1, 1)).smaller

等價於

Pair(Point(0, 0), Point(1, 1)).smaller(Point.OrderingPoint)

也就是說

implicitly[Ordering[Point]] == Point.OrderingPoint

Context Bound

import scala.math.Ordering

case class Pair[T : Ordering](first: T, second: T) {
  def smaller(implicit order: Ordering[T]) = order.min(first, second)
}

可以使用implicitly簡化

import scala.math.Ordering
case class Pair[T : Ordering](first: T, second: T) {
  def smaller = implicitly[Ordering[T]].min(first, second)
}

可以進一步簡化

import scala.math.Ordering

case class Pair[T : Ordering](first: T, second: T) {
  def smaller = Ordering[T].min(first, second)
}

Ordering[T]首先呼叫了object Orderingapply方法,從而便捷地找到了Order[T]的隱式值

object Ordering {
  def apply[T](implicit ord: Ordering[T]) = ord
}

所以Ordering[T].min等價於implicitly[Ordering[T]].min

View Bound

import scala.math.Ordered

case class Pair[T](first: T, second: T){
  def smaller(implicit order: T => Ordered[T]) = {
    if (order(first) < second) first else second
  }
}

implicit order: T => Ordered[T]smaller的區域性作用域內,即是一個隱式引數,又是一個隱式轉換函式

import scala.math.Ordered

case class Pair[T](first: T, second: T){
  def smaller(implicit order: T => Ordered[T]) = {
    if (first < second) first else second
  }
}

又因為在Predef預定義了從IntRichInt的隱式轉換,而RichIntOrdered[Int]的子型別,所以在Predef定義的implicit Int => RichInt的隱式轉換函式可作為隱式引數implicit order: T => Ordered[T]的隱式值。

Pair(1, 2).smaller

等價於

Pair(1, 2).smaller(Predef.intWrapper _)

上述簡化的設計,使得隱式引數order沒有必要存在,這樣的模式較為常見,可歸一為一般模式:View Bound

import scala.math.Ordered

case class Pair[T <% Ordered[T]](first: T, second: T) {
  def smaller = if (first < second) first else second
}

需要注意的是:T <% Ordered[T]表示:T可以隱式轉換為Ordered[T];而T <: Ordered[T]表示:TOrdered[T]的一個子型別。

Upper Bound

import scala.math.Ordered

case class Pair[T <: Comparable[T]](first: T, second: T) {
  def smaller = if (first.compareTo(second) < 0) first else second
}
Pair("1", "2").smaller  // OK, String is subtype of Comparable[String]
Pair(1, 2).smaller      // Compile Error, Int is not subtype of Comparable[Int]

相關文章