@xtccc
2016-03-09T10:34:45.000000Z
字数 8069
阅读 2166
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 identifier
import other_conversions.OtherConversion.RMB2Dollar
object 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 = v
def toRMB(): Double = Dollar.rate2RMB*getValue
def addRMB(rmb: RMB) :Dollar = new Dollar(v + RMB.rate2Dollar*rmb.getValue)
override
def toString() = s"""Dollar: ${getValue()},\tRMB: ${toRMB()}"""
}
object Dollar {
def rate2RMB: Double = 8.0 // 美元兑人民币的汇率为1:8
implicit def Dollar2RMB(dollar: Dollar): RMB = new RMB(Dollar.rate2RMB*dollar.getValue)
}
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions
class RMB(private val v: Double) {
def getValue(): Double = v
def toDollar(): Double = RMB.rate2Dollar*v
def addDollar(doll: Dollar): RMB = new RMB(getValue + Dollar.rate2RMB*doll.getValue)
override
def 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.conversions
class Euro(private val v: Double) {
def getValue(): Double = v
def addEuro(euro: Euro): Euro = new Euro(getValue() + euro.getValue())
def toRMB(): Double = Euro.rate2RMB*getValue()
override
def toString(): String = s"""Euro: ${getValue()},\tRMB: ${toRMB()}"""
}
object Euro {
def rate2RMB = 10.0
}
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions.other_conversions
import 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.conversions
object Example {
def main(args: Array[String]): Unit = {
val jpy = new JPY(1000)
println(1000.0 + jpy) // `Double` -> `JPY`
}
}
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions
class JPY(private val v: Double) {
def getValue(): Double = v
def + (x: JPY): JPY = new JPY(x.getValue() + getValue())
override
def toString(): String = s"""JPY: ${getValue()},\tRMB: ${getValue()/JPY.rate2RMB}"""
}
object JPY {
def rate2RMB: Double = 20.0
implicit 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.conversions
object 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.conversions
object Example {
def main(args: Array[String]): Unit = {
// bring `other_conversions.TaoPrompts.prompt1` into scope
import other_conversions.TaoPrompts._
Greeter.greet("Tao") // 隐式地使用提示符$
Greeter.greet("Tao")(new Prompt(">")) // 显式地使用提示符>
}
}
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions
object Greeter {
def greet(name: String)(implicit prompt: Prompt) = {
println(s"""${prompt.msg} Welcom ${name}""")
println(s"""${prompt.msg}""")
}
}
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions
class Prompt(val msg: String) { }
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions.other_conversions
import cn.gridx.scala.example.implicits.conversions.Prompt
object TaoPrompts {
implicit def prompt1 = new Prompt("$")
}
这里的prompy其实只是一个String,为什么还要将其放在一个class中呢?因为implicit parameter是根据类型进行匹配的,所以自定义一个class就可以使得这个class很独特。
package cn.gridx.scala.example.implicits.conversions
object Example {
def main(args: Array[String]): Unit = {
// bring other_conversions.TaoPrompts.{prompt, drink} into scope
import other_conversions.TaoPrefences._
Greeter.greet("Tao") // 隐式地使用提示符$
Greeter.greet("Tao")(new Prompt(">"), new Drink("Sprit"))
}
}
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions
object 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.conversions
class Prompt(val msg: String) {
}
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions
class Drink(val drink: String) { }
--------------------------------------------------------------------------------------------
package cn.gridx.scala.example.implicits.conversions.other_conversions
import 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) => x
case x::rest =>
val maxRest = maxListElem(rest)
if (maxRest > x) maxRest
else 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) => x
case 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) => x
case x::rest =>
val maxRest = maxListElem3(rest)(ordering)
if (ordering.gt(x, maxRest)) x // 用`ordering.gt`比较x与maxRest
else maxRest
}