Если посмотреть на исходный код некоторых библиотек Scala, например бесформенный, я часто нахожу черты с именем LowPriorityImplicits
.
Не могли бы вы объяснить эту закономерность? Какая проблема решается и как ее решает шаблон?
Если посмотреть на исходный код некоторых библиотек Scala, например бесформенный, я часто нахожу черты с именем LowPriorityImplicits
.
Не могли бы вы объяснить эту закономерность? Какая проблема решается и как ее решает шаблон?
Этот шаблон позволяет вам иметь иерархию имплицитов, избегая ошибок компилятора, связанных с двусмысленностью, и предоставляя способ расставить приоритеты. В качестве примера рассмотрим следующее:
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"
Также стоит отметить, что этот шаблон не ограничен двумя уровнями, но вы можете создать иерархию признаков и иметь в них неявные определения, которые переходят от более конкретных к более общим, поднимаясь вверх по дереву наследования.
object
(не компаньон), расширив его низкоприоритетную черту и затем явно импортировав объект.
- person Aldo Stracquadanio; 02.04.2020