解释Scala类型级编程中使用的`LowPriorityImplicits`模式

zig*_*tar 10 scala type-level-computation shapeless

在查看一些Scala库的来源时,例如无形,我经常会找到名为的特征LowPriorityImplicits.

你能解释一下这种模式吗?解决了什么问题,模式如何解决?

Ald*_*nio 14

该模式允许您使用implicits层次结构,避免编译器出现与歧义相关的错误,并提供一种方法来确定它们的优先级.举个例子考虑以下内容:

trait MyTypeclass[T] { def foo: String }
object MyTypeclass {
  implicit def anyCanBeMyTC[T]: MyTypeclass[T] = new MyTypeclass[T] { 
    val foo = "any" 
  }

  implicit def specialForString[T](implicit ev: T <:< String): MyTypeclass[T] = new MyTypeclass[T] {
    val foo = "string"
  }
}

println(implicitly[MyTypeclass[Int]].foo) // Prints "any"
println(implicitly[MyTypeclass[Boolean]].foo) // Prints "any"
println(implicitly[MyTypeclass[String]].foo) // Compilation error
Run Code Online (Sandbox Code Playgroud)

你在最后一行得到的错误是:

<console>:25: error: ambiguous implicit values:
  both method anyCanBeMyTC in object MyTypeclass of type [T]=> MyTypeclass[T]
  and method specialForString in object MyTypeclass of type [T](implicit ev: <: <[T,String])MyTypeclass[T]
  match expected type MyTypeclass[String]
       println(implicitly[MyTypeclass[String]].foo)
Run Code Online (Sandbox Code Playgroud)

这不会编译,因为隐式解决方案会发现含糊不清; 在这种情况下,它有点人为,因为我们String使用隐式证据来定义案例,以便在我们可以将其定义为implicit def specialForString: MyTypeclass[String] = ...不具有任何歧义时触发歧义.但是在某些情况下,您需要在定义隐式实例时依赖其他隐式参数,并使用低优先级模式,您可以按如下方式编写它并使其正常工作:

trait MyTypeclass[T] { def foo: String }

trait LowPriorityInstances {
  implicit def anyCanBeMyTC[T]: MyTypeclass[T] = new MyTypeclass[T] { 
    val foo = "any" 
  }
}

object MyTypeclass extends LowPriorityInstances {
  implicit def specialForString[T](implicit ev: T <:< String): MyTypeclass[T] = new MyTypeclass[T] {
    val foo = "string"
  }
}

println(implicitly[MyTypeclass[Int]].foo) // Prints "any"
println(implicitly[MyTypeclass[Boolean]].foo) // Prints "any"
println(implicitly[MyTypeclass[String]].foo) // Prints "string"
Run Code Online (Sandbox Code Playgroud)

值得注意的是,这种模式并不局限于两个层,但您可以创建一个特征层次结构,并在其中包含从更具体到更通用的上传继承树的隐式定义.