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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章