When looking at the source of some Scala libraries, e.g. shapeless, I often find traits named LowPriorityImplicits
.
Can you please explain this pattern? What is the problem that is solved, and how does the pattern solve it?
That pattern allows you to have hierarchy of implicits avoiding ambiguity-related errors by the compiler and providing a way to prioritise them. As an example consider the following:
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
The error you get in the last line is:
<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)
This wouldn't compile because the implicit resolution will find ambiguity; in this case it is a bit artificial in that we are defining the String
case using the implicit evidence in order to trigger the ambiguity when we could just define it as implicit def specialForString: MyTypeclass[String] = ...
and not have any ambiguity. But there are cases where you need to depend on other implicit parameters when defining implicit instances and using the low-priority pattern you can write it as follows and have it work fine:
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"
It is also worth noting that this pattern is not limited to two layers but you can create a hierarchy of traits and have in them implicit definitions that go from more specific to more generic going up the inheritance tree.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With