Проблема
У меня есть набор типов и набор преобразований между ними. Это звучит как DAG и имеет некоторое сходство с ним. Я хотел бы иметь возможность вычислять неявно кратчайший путь преобразования между любыми двумя типами, если это возможно.
Я подготовил простой пример, который показывает мои тщетные попытки объявить такие импликации.
final case class A(u : Int)
final case class B(u : Int)
final case class BB(u : Int)
final case class C(u : Int)
final case class D(u: Int)
trait Convert[F,T] {
def convert(source : F) : T
}
Я представляю следующие преобразования тестовых примеров: A → B, A → BB, B → C, B → D, C → D.
Я попробовал два подхода, и они дают мне разные неявные ошибки разрешения.
Транзитная цепочка
trait ConcreteConvert[F,T] extends Convert[F,T]
class Transit[F,M,T](implicit fm : ConcreteConvert[F,M], mt : Convert[M,T]) extends Convert[F,T] {
override def convert(source : F) : T =
mt.convert( fm.convert(source) )
}
object Implicits {
implicit def transit[F,M,T](implicit fm : ConcreteConvert[F,M], mt : Convert[M,T]) : Convert[F,T] =
new Transit()(fm, mt)
implicit object A2B extends ConcreteConvert[A,B] {
override def convert(source : A) : B = B(source.u)
}
implicit object B2C extends ConcreteConvert[B,C] {
override def convert(source : B) : C = C(source.u)
}
/*implicit object A2BB extends ConcreteConvert[A,BB] {
override def convert(source : A) : BB = BB(source.u)
}*/ // compilation fails
implicit object C2D extends ConcreteConvert[C,D] {
override def convert(source : C) : D = D(source.u)
}
implicit object B2D extends ConcreteConvert[B,D] {
override def convert(source : B) : D = D(source.u)
}
}
object Usage {
import Implicits._
def conv[F,T](source : F)(implicit ev : Convert[F,T]) : T =
ev.convert(source)
val a = A(0)
val b = conv[A,B](a)
val c = conv[A,C](a)
val d = conv[A,D](a)
}
Такой подход обеспечил возможное разрешение пути между A → B → C → D и A → B → D, компилятор выбирает последний маршрут. Но он терпит неудачу, если есть ветвление
Продолжение передачи
abstract class PostConvert[F, M, T](mt : Convert[M,T]) extends Convert[F,T] {
def pre(source : F) : M
override def convert(source : F) : T =
mt.convert( pre(source) )
}
class IdConvert[F]() extends Convert[F,F] {
override def convert(source : F) : F =
source
}
object ImplicitsPost {
implicit def idConvert[F] : Convert[F,F] =
new IdConvert[F]()
implicit def a2b[T](implicit mt : Convert[B,T]) = new PostConvert[A,B,T](mt) {
override def pre(source : A) : B = B(source.u)
}
implicit def a2bb[T](implicit mt : Convert[BB,T]) = new PostConvert[A,BB,T](mt) {
override def pre(source : A) : BB = BB(source.u)
}
implicit def b2c[T](implicit mt : Convert[C,T]) = new PostConvert[B,C,T](mt) {
override def pre(source : B) : C = C(source.u)
}
implicit def c2d[T](implicit mt : Convert[D,T]) = new PostConvert[C,D,T](mt) {
override def pre(source : C) : D = D(source.u)
}
/*implicit def b2d[T](implicit mt : Convert[D,T]) = new PostConvert[B,D,T](mt) {
override def pre(source : B) : D = D(source.u)
}*/ // compiler fails
}
object UsagePost {
import ImplicitsPost._
def conv[F,T](source : F)(implicit ev : Convert[F,T]) : T =
ev.convert(source)
val a = A(0)
val b = conv[A,B](a)
val c = conv[A,C](a)
val d = conv[A,D](a)
}
В этом случае компилятор может игнорировать несоответствующее преобразование A → BB. Но он не разрешает конфликт A → B → C → D и A → B → D
Что я ищу
Некоторые способы решения проблемы в общем виде. Я мог бы определить график отношений и позволить имплицитной механике выбрать самый короткий путь в нем. Было бы лучше, если бы я мог настроить каждый конверсионный вес, чтобы сделать A → B → D и A → C → D различимыми. Существует некоторая черная магия позади неявного приоритета разрешения, и я надеюсь, что это могло бы помочь.
Implicits, как говорят, очень мощный вычислительный инструмент, за несколько минут работы компилятора стоит в сложных случаях. Поэтому я надеюсь, что произвольные длительные транзитивные преобразования возможны с помощью какой-то сложной техники.