我有一组类型和一组转换。这听起来像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据说是非常强大的计算工具,几分钟的编译器工作值得在错综复杂的情况下使用。因此,我希望通过一些棘手的技术可以进行任意长传递转换。
答案 0 :(得分:4)
您无法使用scala隐式解决方案解决此问题,因为它不支持回溯。
你最好的选择可能是修改scala编译器以支持隐式解决方案的回溯,这应该足以让你的第一个实现工作。
我撒谎,应该可以使用编译器的当前状态,但它不会像你要编写的等效Prolog程序一样好,并且可能超出了& #34;哦,在类型级别编写应该很有趣#34; )。让我先解释一下你的问题。
给出几种类型:
trait A; trait B; trait B; trait C; trait D
这些类型的有向图:
trait Edge[X, Y]
def fact[X, Y] = new Edge[X, Y] {}
implicit val edge0: Edge[A, B] = fact // ( A )
implicit val edge1: Edge[A, BB] = fact // ↓ ↓
implicit val edge2: Edge[B, C] = fact // ( B ) BB
implicit val edge3: Edge[B, D] = fact // ↓ ↓
implicit val edge4: Edge[C, D] = fact // C → D
使用隐式解析找到A
和D
之间的最短路径。
要了解此问题的棘手问题,将其分解为两部分非常有用:
将这些implicit edges
提升为从节点A
开始的图表的类型级别表示,如:
A
:: (B
:: (C :: (D :: HNil) :: HNil)
:: (D :: HNil)
:: HNil)
:: (BB :: HNil)
:: HNil
对此表示形式执行类型级别BFS。
令人惊讶的是,1听起来比较棘手,而且因为scala隐式解决方案不会回溯。为了实现这一点,您必须采用稍微不同的图表表示。
一种坚持原始配方(每个边缘隐含一个)的解决方案可能是使用类似this example中曝光的技术,该技术使用两个trait EdgeLeft[X, Y]
& trait EdgeRight[X, Y]
代替trait Edge[X, Y]
,并将图表的所有边缘收集到一个HList
中,以解决缺乏回溯的问题。
通过将图形编码在更接近邻接矩阵的表示中,例如使用implicit fact: Neighbours[A, B :: BB :: HNil]
,您还可以使您的生活更加简单。但无论哪种方式,都应该对图表表示稍作修改,以便构建一个与上图表示形式等效的结构。
解决2.不容易,但从理论上说,在以下输入上编写纯值,值DFS并将其提升到类型级别应该更难:
val input: List[Any] = (
"A"
:: ("B"
:: ("C" :: ("D" :: Nil) :: Nil)
:: ("D" :: Nil)
:: Nil)
:: ("BB" :: Nil)
:: Nil
)
def DFS(i: List[Any]): List[String] = ???
答案 1 :(得分:2)
我认为用Scala中当前可用的工具解决这个问题是不可能的。
让我们退后一步,问一下我们如何解决这个问题呢?如果你暂时考虑这个问题,你会发现它等同于在图上求解最短路径。在这种情况下,图的节点是具体类(此处为A, B, BB, C, and D
),边是Convert
类型类中的值。
解决最短路径的标准方法是Dijkstra's Algorithm,它只是简化为广度优先搜索未加权图形,这就是我们在这里所拥有的。那么我们如何通过这个图进行广度优先搜索?
Per wikipedia这里是广度优先搜索的伪代码:
1 Breadth-First-Search(Graph, root):
2
3 for each node n in Graph:
4 n.distance = INFINITY
5 n.parent = NIL
6
7 create empty queue Q
8
9 root.distance = 0
10 Q.enqueue(root)
11
12 while Q is not empty:
13
14 current = Q.dequeue()
15
16 for each node n that is adjacent to current:
17 if n.distance == INFINITY:
18 n.distance = current.distance + 1
19 n.parent = current
20 Q.enqueue(n)
在这个算法中有两个地方我们需要枚举图中与某个谓词匹配的所有节点。在第3行,我们需要枚举图中的所有节点。这原则上是可行的,并且假设节点形成一个ADT,它们很容易就可以提供前进的路径。
但是,在第16行,我们需要将相邻节点枚举到我们拥有的节点。为此,我们需要分析图中的所有边,这需要枚举某个形状的所有含义:如果A
是我们的节点,那么我们希望所有类型成员匹配Convert[A,_]
。
Scala无法通过implicits机制枚举这些类型类成员。原因是请求隐式的机制允许您最多请求一个隐式,并且如果发现任何模糊(即多个同等排名)的隐含,则将其视为错误。考虑如果我们请求节点B
的所有边缘会发生什么:
def adjacentToB [OtherNode](隐式边:转换[B,OtherNode])=边
因为Convert[B,C]
和Convert[B,D]
都满足上述调用,编译器会给我们一个错误。
旁注:您可能认为可以使用更深入思考的深度优先搜索来解决这个问题。不幸的是,我认为你会被完全相同的问题所困扰。