@xtccc
2016-03-09T02:34:45.000000Z
字数 8069
阅读 2377
Scala
假设一个函数的参数定义是类型A,但是我们给该函数提供的参数的类型是B。在编译时,如果编译器发现参数类型B不符合要求,会导致编译失败,那么它就会去寻找implict conversion。如果能找到有效的implicit conversion,那么就会调用它,将B类型的数据转化为A类型的数据。
Implicit conversion 应用的3个规则:
1. 只有用implicit修饰的函数会被认为是implicit conversion
2. implicit conversion必须以single identifier的形式位于scope内,或者位于source type/target type的companion object中
3. 只有一个implicit conversion会被应用:convert1(convert2(x))是不行的
Implicit conversion 应用的3种场合:
1. Implicit conversion to an expected type
2. Converting the receiver
3. Implicit Parameters
package cn.gridx.scala.example.implicits.conversions// implicit conversion `RMB2Dollar`本package内// 因此需要 import into this scope as a single identifierimport other_conversions.OtherConversion.RMB2Dollarobject Example {def main(args: Array[String]): Unit = {val d1 = new Dollar(2)val d2 = new Dollar(20)val r1 = new RMB(80)val r2 = new RMB(800)val euro= new Euro(15)println(r1.addDollar(r2))println(d1.addRMB(d2))println(euro.addEuro(r1))printDollar(2.0)// 无法通过编译,一次只能应用一个implicit conversion// 不能 double2Dollar(Dollar.Dollar2RMB(4.0))printRMB(4.0)}def printDollar(dollar: Dollar) = println(dollar)def printRMB(rmb: RMB) = println(rmb)implicit def double2Dollar(v: Double): Dollar = new Dollar(v)}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversions;class Dollar(private val v: Double) {def getValue() :Double = vdef toRMB(): Double = Dollar.rate2RMB*getValuedef addRMB(rmb: RMB) :Dollar = new Dollar(v + RMB.rate2Dollar*rmb.getValue)overridedef toString() = s"""Dollar: ${getValue()},\tRMB: ${toRMB()}"""}object Dollar {def rate2RMB: Double = 8.0 // 美元兑人民币的汇率为1:8implicit def Dollar2RMB(dollar: Dollar): RMB = new RMB(Dollar.rate2RMB*dollar.getValue)}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversionsclass RMB(private val v: Double) {def getValue(): Double = vdef toDollar(): Double = RMB.rate2Dollar*vdef addDollar(doll: Dollar): RMB = new RMB(getValue + Dollar.rate2RMB*doll.getValue)overridedef toString() = s"""RMB: ${v},\tDollar: ${toDollar()}"""}object RMB {def rate2Dollar: Double = 1/8.0 // 人民币兑美元的汇率为1/8// 这个implicit conversion可以位于`object RMB`中,也可以位于`object Euro`中implicit def rmb2Euro(rmb: RMB):Euro = new Euro(rmb.getValue()/Euro.rate2RMB)}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversionsclass Euro(private val v: Double) {def getValue(): Double = vdef addEuro(euro: Euro): Euro = new Euro(getValue() + euro.getValue())def toRMB(): Double = Euro.rate2RMB*getValue()overridedef toString(): String = s"""Euro: ${getValue()},\tRMB: ${toRMB()}"""}object Euro {def rate2RMB = 10.0}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversions.other_conversionsimport cn.gridx.scala.example.implicits.conversions.{Dollar, RMB}object OtherConversion {implicit def RMB2Dollar(rmb: RMB): Dollar = new Dollar(rmb.getValue()*RMB.rate2Dollar)}
package cn.gridx.scala.example.implicits.conversionsobject Example {def main(args: Array[String]): Unit = {val jpy = new JPY(1000)println(1000.0 + jpy) // `Double` -> `JPY`}}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversionsclass JPY(private val v: Double) {def getValue(): Double = vdef + (x: JPY): JPY = new JPY(x.getValue() + getValue())overridedef toString(): String = s"""JPY: ${getValue()},\tRMB: ${getValue()/JPY.rate2RMB}"""}object JPY {def rate2RMB: Double = 20.0implicit def double2JPY(x: Double): JPY = new JPY(x)}
在case 2 中,还可以引申出 simulating new syntax 这种用法。
我们可以如下创建一个Map实例:
val map = Map(1 -> "One", 2 -> "Two", 3 -> "Three")
这里的操作符->是怎么来的?
它实际上是class ArrowAssoc中的一个方法,并且scala.Predef中定义了一个从Any到ArrowAssco的implicit conversion。当遇到1时,1会从Any转换为ArrowAssco,进而可以使用操作符->。
object Predef extends LowPriorityImplicits {final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal {@inline def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y)def →[B](y: B): Tuple2[A, B] = ->(y)}@inline implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)}
下面我们来验证,自己定义一个类似的操作符 -->>:
package cn.gridx.scala.example.implicits.conversionsobject Example {def main(args: Array[String]): Unit = {val map: Map[Int, String] = Map(1 -> "One", 2 -->> "Two", 3 -->> "Three")map.foreach(println)}final class MyArrowAssco[A](val _leftValue:A) extends AnyVal {def -->> [B](y:B): Tuple2[A, B] = Tuple2(_leftValue, y)}implicit def any2MyArrowAssco[A](x: A): MyArrowAssco[A] = new MyArrowAssco(x)}
在case 3中,编译器会将 someCall(a) 变为 someCall(a)(b) ,或者是将 new SomeClass(a) 变为 new SomeClass(a)(b) ,注意编译器添加的是最后一个curried paramater list,而不是最后一个parameter。
package cn.gridx.scala.example.implicits.conversionsobject Example {def main(args: Array[String]): Unit = {// bring `other_conversions.TaoPrompts.prompt1` into scopeimport other_conversions.TaoPrompts._Greeter.greet("Tao") // 隐式地使用提示符$Greeter.greet("Tao")(new Prompt(">")) // 显式地使用提示符>}}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversionsobject Greeter {def greet(name: String)(implicit prompt: Prompt) = {println(s"""${prompt.msg} Welcom ${name}""")println(s"""${prompt.msg}""")}}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversionsclass Prompt(val msg: String) { }--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversions.other_conversionsimport cn.gridx.scala.example.implicits.conversions.Promptobject TaoPrompts {implicit def prompt1 = new Prompt("$")}
这里的prompy其实只是一个String,为什么还要将其放在一个class中呢?因为implicit parameter是根据类型进行匹配的,所以自定义一个class就可以使得这个class很独特。
package cn.gridx.scala.example.implicits.conversionsobject Example {def main(args: Array[String]): Unit = {// bring other_conversions.TaoPrompts.{prompt, drink} into scopeimport other_conversions.TaoPrefences._Greeter.greet("Tao") // 隐式地使用提示符$Greeter.greet("Tao")(new Prompt(">"), new Drink("Sprit"))}}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversionsobject Greeter {def greet(name: String)(implicit prompt: Prompt, drink: Drink) = {println(s"""${prompt.msg} Welcom ${name},|we have a cup of ${drink.drink} for you !""".stripMargin)println(s"""${prompt.msg}""")}}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversionsclass Prompt(val msg: String) {}--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversionsclass Drink(val drink: String) { }--------------------------------------------------------------------------------------------package cn.gridx.scala.example.implicits.conversions.other_conversionsimport cn.gridx.scala.example.implicits.conversions.{Drink, Prompt}object TaoPrefences {implicit def prompt = new Prompt("$")implicit def drink = new Drink("Coca-cola")}
假设我们想找出一个列表中最大的元素,很显然,这要求列表中的元素之间是可以比较大小的,因此我们在定义方法时限定列表中元素T必须是Ordered的子类。
def maxListElem[T <: Ordered[T]](list: List[T]): T =list match {case List() => throw new IllegalArgumentException("Empty list")case List(x) => xcase x::rest =>val maxRest = maxListElem(rest)if (maxRest > x) maxRestelse x}
看起来不错,但是有个问题:该方法不适用于List[Int]参数,因为Int不是Ordered[T]的子类!
解决方法
方法1. 寻找一个T -> Ordered[T]的方法,对于任何类型T,将其变为类型Ordered[T]之后再进行比较大小。
/*** 将T转化为Ordered[T]之后再进行比较*/def maxListElem2[T](list: List[T])(implicit orderer: T => Ordered[T]): T =list match {case List() => throw new IllegalArgumentException("Empty list")case List(x) => xcase x::rest =>val maxRest = maxListElem2(rest)(orderer)if (orderer(x) > maxRest) x // orderer(x)将x由T转化为类型Ordered[T]else maxRest}
这里,在运行时,系统会自动寻找一个实现了T -> Ordered[T]变换的隐式方法。
方法2. 寻找一个Ordering的实现类,然后利用这个类中的gt方法来进行大小的比较。
/*** 使用Ordering[T]类的`gt`方法来进行比较*/def maxListElem3[T](list: List[T])(implicit ordering: Ordering[T]): T =list match {case List() => throw new IllegalArgumentException("Empty list")case List(x) => xcase x::rest =>val maxRest = maxListElem3(rest)(ordering)if (ordering.gt(x, maxRest)) x // 用`ordering.gt`比较x与maxRestelse maxRest}