Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explain the `LowPriorityImplicits` pattern used in Scala type-level programming

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?

like image 385
ziggystar Avatar asked Nov 05 '15 12:11

ziggystar


1 Answers

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.

like image 132
Aldo Stracquadanio Avatar answered Nov 16 '22 02:11

Aldo Stracquadanio