Объясните шаблон `LowPriorityImplicits`, используемый в программировании на уровне типов Scala.

Если посмотреть на исходный код некоторых библиотек Scala, например бесформенный, я часто нахожу черты с именем LowPriorityImplicits.

Не могли бы вы объяснить эту закономерность? Какая проблема решается и как ее решает шаблон?


person ziggystar    schedule 05.11.2015    source источник


Ответы (1)


Этот шаблон позволяет вам иметь иерархию имплицитов, избегая ошибок компилятора, связанных с двусмысленностью, и предоставляя способ расставить приоритеты. В качестве примера рассмотрим следующее:

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

Ошибка в последней строке:

<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)

Это не будет компилироваться, потому что неявное разрешение обнаружит двусмысленность; в данном случае это немного искусственно, поскольку мы определяем случай 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"

Также стоит отметить, что этот шаблон не ограничен двумя уровнями, но вы можете создать иерархию признаков и иметь в них неявные определения, которые переходят от более конкретных к более общим, поднимаясь вверх по дереву наследования.

person Aldo Stracquadanio    schedule 05.11.2015
comment
Работает ли это с классами типов, которые вы не контролируете, или модифицирует требуемый сопутствующий объект? - person gregghz; 01.04.2020
comment
Я не совсем уверен, должен ли я быть честным, но вы можете провести эксперимент, поместив экземпляры в общий object (не компаньон), расширив его низкоприоритетную черту и затем явно импортировав объект. - person Aldo Stracquadanio; 02.04.2020