Scala的隐式转换系统提供了一套定义良好的查找机制,让编译器能够调整代码。当用Scala写代码时可以故意漏掉一些信息,而让编译器去尝试在编译期自动推导出来。
Scala编译器可以推导下面两种情况:
使用隐式转换能够减少代码,能够向已有类型中注入新的方法,也能够创建领域特定语言(DSL)。
// 需求场景:
// 提供一个计算销售税的方法
// 条件:
// 1. 某些应用需要知道当前事务发生的具体地点,以便增收税
// 2. 为了促进购物消费,某些地方可能将年假最后几天定位“免税期”
def calcTax(amount: Float)(implicit rate: Float): Float = amount*rate
object SimpleStateSalesTax {
implicit val rate: Float = 0.05F
}
case class ComplicatedSalesTaxData(
baseRate: Float,
isTaxHoliday: Boolean,
storeId: Int
)
object ComplicatedSalesTax {
private def extraTaxRateForStore(id: Int): Float = {
// 可以通过id推断商铺地点,再计算附加税
0.0F
}
implicit def rate(implicit cstd: ComplicatedSalesTaxData): Float =
if (cstd.isTaxHoliday) 0.0F
else cstd.baseRate + extraTaxRateForStore(cstd.storeId)
}
defined function calcTax defined object SimpleStateSalesTax defined class ComplicatedSalesTaxData defined object ComplicatedSalesTax
{
import SimpleStateSalesTax.rate
val amount = 100F
println(s"Tax on $amount = ${calcTax(amount)}")
}
Tax on 100.0 = 5.0
import SimpleStateSalesTax.rate amount: Float = 100.0F
{
import ComplicatedSalesTax.rate
implicit val myStore = ComplicatedSalesTaxData(0.06F, false, 1010)
val amount = 100F
println(s"Tax on $amount = ${calcTax(amount)}")
}
Tax on 100.0 = 6.0
import ComplicatedSalesTax.rate myStore: ComplicatedSalesTaxData = ComplicatedSalesTaxData(0.06F, false, 1010) amount: Float = 100.0F
使用Predef对象定义的implicitly方法与附加类别签名结合,可以使用一种便捷的方式定义一个接受参数化类型隐式参数的函数。
import math.Ordering
case class MyList[A](list: List[A]) {
def sortBy1[B](f: A => B)(implicit ord: Ordering[B]): List[A] =
list.sortBy(f)(ord)
def sortBy2[B: Ordering](f: A => B): List[A] =
list.sortBy(f)(implicitly[Ordering[B]])
}
import math.Ordering defined class MyList
val list = MyList(List(1, 3, 5, 2, 4))
list: MyList[Int] = MyList(List(1, 3, 5, 2, 4))
list sortBy1 (i => -i)
res5: List[Int] = List(5, 4, 3, 2, 1)
list sortBy2 (i => -i)
res6: List[Int] = List(5, 4, 3, 2, 1)
MyList类提供了两种sorBy方法:
B: Ordering
被上下文定界(context bound),它安置隐式参数列表将接受Ordering[B]实例。implicitly方法会对传给函数的所有标记为隐式参数的实例进行解析。
object M {
def m(seq: Seq[Int]): Unit = println(s"Seq[Int]: $seq")
def m(seq: Seq[String]): Unit = println(s"Seq[String]: $seq")
}
将会出现这样的报错:
Compilation Failed
Main.scala:50: double definition:
def m(seq: Seq[Int]): Unit at line 49 and
def m(seq: Seq[String]): Unit at line 50
have same type after erasure: (seq: Seq)Unit
def m(seq: Seq[String]): Unit = println(s"Seq[String]: $seq")
^
因为上面的两个m方法的字节码是一样的,编译器不允许同时出现这些方法定义。
不过我们可以添加隐式参数来消除这些方法的二义性。
object M {
implicit object IntMarker
implicit object StringMarker
def m(seq: Seq[Int])(implicit i: IntMarker.type): Unit = println(s"Seq[Int]: $seq")
def m(seq: Seq[String])(implicit s: StringMarker.type): Unit = println(s"Seq[String]: $seq")
}
defined object M
import M._
import M._
m(List(1, 2, 3))
Seq[Int]: List(1, 2, 3)
m(List("one", "two", "three"))
Seq[String]: List(one, two, three)
IntMarker
res11: IntMarker.type = cmd7$$user$M$IntMarker$@39546a
StringMarker
res12: StringMarker.type = cmd7$$user$M$StringMarker$@a1f49b
为了尽量避免使用Int和String这样常用类型作为隐式参数和对应值(这些类型还可能出现多处定义,而导致二义性和编译器抛出错误),比较安全的做法是专门设计特有的类型作为隐式参数。
@implicitNotFound注解告诉编译器在不能构造出带有该注解的类型的参数时给出错误提示。这样做事给程序员有意义的错误提示。
比如CanBuildFrom构造器和<:<类有相关注解:
@implicitNotFound(msg = "Cannot construct a collection of type ${To} with elements of type ${Elem} based on a collection of type ${From}.")
trait CanBuildFrom[-From, -Elem, +To] {...}
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable