Scala 2 Implicit

19-03-30 编程 #code #scala

隐式参数

//隐式参数是在调用时可以自动填充的参数,需要在调用范围内(scope) 有一个隐式变量可供填充。
def addInt(i:Int)(implicit n: Int) = i + n


//需要提供一个隐式变量 n
implicit val sn = 1
addInt(2) // 3

//如果有两个满足类型的隐式变量,则在编译 addInt(2) 时报错

//scala 的方法中 ExecutionContext 一般作为 implicit 参数。

隐式转换方法

如果想要给 String 实现一个 mkStr 方法,简单的给 String 添加一个 Ops! 前缀再返回。

//1. 首先实现一个包含目标方法的类型,实现该方法
class StrOps(s:String) {
  def mkStr(): String = {
    return "Ops! " + s
  }
}

//2. 告诉 scala 编译器 String 可以通过类型转换获得 mkStr 这个方法:
implicit final def string2StrOps(s: String) = new StrOps(s)

//3. 现在用户可以直接认为 String 有 mkStr 方法
val s = "who changed my string"
s.mkStr() //res2: String = Ops! who changed my string

//在 scala.Predef 中定义了大量的隐式转换,例如 RichInt,RichDouble,StringOps 这些

implicit class

可以看到第 2 步非常的冗余,于是SIP-13提出一个 implicit class,将上面的 1,2 步合并:

implicit class StrOps(s:String) {
  def mkStr(): String = {
    return "Ops! " + s
  }
}

注意,这个只是一个语法糖。去糖后就是上面的那个形式。implicit class 有 3 个约束和一个注解问题:

  1. 必须要有主一个构造函数且只能一个构造参数(implicit 参数除外).构造参数就是源类型。这个构造函数即等价上面第 2 步的隐式转换方法:

    implicit class RichDate(date: java.util.Date) // OK!
    implicit class Indexer[T](collecton: Seq[T], index: Int) // BAD!
    implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // OK!
    
  2. 只能定义在其他 trait/class/object 中:

    object Helpers {
        implicit class RichInt(x: Int) // OK!
    }
    implicit class RichDouble(x: Double) // BAD!
    
  3. 在当前 scope 内,不允许有和 implicit class 同名的方法,对象,变量。因为 case class 会自动生成同名 object 对象,所以 implicit class 不能是 case class.

    object Bar
    implicit class Bar(x: Int) // BAD!
    
    val x = 5
    implicit class x(y: Int) // BAD!
    
    //cuz case class has companion object by default 
    implicit case class Baz(x: Int) // BAD! conflict with the companion object
    
  4. 还有就是 implicit class 的注解在去语法糖后会自动添加到类和方法,除非在注解中指明范围:

    @bar
    implicit class Foo(n: Int)
    
    //desugar
    @bar implicit def Foo(n: Int): Foo = new Foo(n)
    @bar class Foo(n:Int)
    
    //除非在注解中指明:genClass / method
    @(bar @genClass) implicit class Foo(n: Int)
    
    //desugar 得到
    @bar class Foo(n: Int)
    implicit def Foo(n: Int): Foo = new Foo(n)
    

implicitly 方法

scala 的 PreDef 中有有一个 implicitly 方法,表示在当前 scope 征召一个隐式变量并返回该变量。

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

implitly[T] means return implicit value of type T in the context

implicit class Foo(val i: Int) {
   def addValue(v: Int): Int = i + v
} 

implicit val foo:Foo = Foo(1)
val fooImplicitly = implicitly[Foo] // Foo(1)

value class

scala 还有一个概念:value class

class Wrapper(val underlying: Int) extends AnyVal
//1. 一个 public val 参数表示 runtime 类型,这里是 Int. 编译时是 Wrapper 类型,所以 value class 目的是降低分配开销。
//2. value class 需要 extends AnyVal
//3. value class 只能有 defs, 不能有 vals, vars, or nested traits, classes or objects,
//   因为 def 是通过静态方法实现的,而 val,var 这些则必须创建相应类型了。
//4. value class 只能扩展 通用 trait(universal traits),
//   universal traits 是 A universal trait is a trait that extends Any, only has defs as members, and does no initialization.

extension method

当 implicit class 类型参数是AnyVal子类时,value class 和上面的 implicit class 形式相近,所以可以通过 value class 降低 implicit class 的分配开销。例如 RichtInt

implicit class RichInt(val self: Int) extends AnyVal {
  def toHexString: String = java.lang.Integer.toHexString(self)
}

因为 RichInt 是 value class,在运行时(runtime)不会有 RichInt 这个类,而是 Int,而 3.toHexString 实际是通过静态方法实现的: RichInt$.MODULE$.extension$toHexString(3),这么做好处是减少对象分配开销(avoid the overhead of allocation).如果 implicit class 的类型参数不是 AnyVal 子类,那么在 runtime 时会有相应类型对象被创建,用户察觉不到区别。

value class 还有其他作用和局限性,可以参考上面链接。