对原始问题进行了大量编辑:现在我提前展示了整个代码,而没有显示解释我动机的变体。对造成的混乱深表歉意。
我需要一个简单的类型类来实现对类型的成员类型之一的投影——为了这个例子的目的,让我们把它变成一个直接的类型转换:
trait Subject {
type E
type Const
}
object Subject {
implicit def projection :Projection[Subject] { type Project[X] = Subject { type E = X } } = ???
}
abstract class Projection[S <: Subject] {
type Project[X] <: Subject { type E = X }
}
implicit class ProjectSubject[S <: Subject](private val self :S) extends AnyVal {
def project[X](implicit p :Projection[S]) :p.Project[X] = ???
}
class Box[X] extends Subject { type E = X }
object Box {
implicit def projection[A] :Projection[Box[A]] { type Project[X] = Box[X] } = ???
}
class Adapter[S <: Subject] extends Subject { type E = S#E }
object Adapter {
implicit def adapterProjection[S <: Subject](implicit p :Projection[S])
:Projection[Adapter[S]] { type Project[X] = Adapter[p.Project[X]] } = ???
}
val res = new Adapter[Box["E"]].project["F"]
在上面的例子中,很明显,投影应该是递归的,Subject
子类声明它们自己的规则。显然,我希望投影在效果上是相反的:
class Specific extends Adapter[Box["E"]]
val spec = (new Specific).project["F"] //doesn't compile
如果Specific
不提供自己的投影,则应使用Adapter
的投影,最后一个表达式的计算结果为Adapter[Box["F"]]
。如果我解密Projection[-S <: Subject]
,这会很好地工作,但问题是我需要投影来保留一些属性,这里表示为Const
成员类型:
class Projection[S <: Subject] {
type Project[X] <: Subject { type E = X; type Const = S#Const }
}
为了清楚起见,我从上面的代码中删除了这个约束,因为它不会导致问题。
在前面的示例中,编译器将抱怨缺少隐式Projection[Specific]
,而不尝试上转换该值。如何使其与使用站点的差异进行编译?
不与存在:
implicit class ProjectSubject[S <: Subject](private val self :S) extends AnyVal {
def project[X](implicit p :Projection[_ >: S <: Subject]) = ???
}
我的猜测是,这里的通配符等效于Subject
,并且不会从未桥接问题的编译器-Xlog-implicits
日志中搜索到除Projection[Subject]
之外的隐含词(该问题具有较大的Subject层次结构,具有更多的隐含投影声明(。
然后,我尝试了一个隐含的中间反变体,它有时有效:
abstract class ProjectionAvailable[-S <: T, T <: Subject] //extends (S => T)
implicit def ProjectionAvailable[S <: Subject](implicit p :Projection[S]) :ProjectionAvailable[S, S] = ??? //(s :S) => s
implicit def ProjectionSubject[S <: T, T <: Subject](s :S)(implicit witness :ProjectionAvailable[S, T]) =
new ProjectionSubject[T](s)
class ProjectionSubject[S <: Subject](private val self :S) extends AnyVal {
def project[X](implicit p :Projection[S]) :p.Project[X] = p.asInstanceOf[p.Project[X]]
}
这看起来很有希望,但不幸的是,编译器和以前一样:查看可用的隐式,将类型参数实例化为ProjectionAvailable[Specific, T]
,并抱怨缺少Projection
,而没有利用其逆变。我尝试了的变体
class ProjectionAvailable[S <: T, T <: Subject]
除了更明显的误差之外没有任何实际的差异。我试着整合ProjectionAvailable
变成了Projection
,但它也没有改变:
class Projection[-S <: T, T] { /* as before */ }
我的直觉是,这可能是可行的,但需要在类型推理中巧妙地指导编译器,目前我还没有新的探索途径。
我无法重现您提到的行为(这就是为什么我在评论中问您如何测试def project[X](implicit p :Projection[_ >: S <: Subject]) = ???
或使用ProjectionAvailable
的方法对您不起作用(。
用你在ProjectSubject
中使用存在Projection
的方法,我额外定义了Projection[Specific]
,代码编译时不会出现错误
Error: ambiguous implicit values:
both value specificProjection in object App of type App.Projection[App.Specific]{type Project[X] = App.Specific}
and method adapterProjection in object App of type [S <: App.Subject](implicit p: App.Projection[S]): App.Projection[App.Adapter[S]]{type Project[X] = App.Adapter[p.Project[X]]}
match expected type App.Projection[_ >: App.Specific <: App.Subject]
val spec = (new Specific).project["F"]
所以Projection[Specific]
的隐式是候选的,我看不出下面的内容怎么会是真正的
这里的通配符等效于
Subject
,并且不会搜索除Projection[Subject]
之外的隐词。
如果我使adapterProjection
的优先级低于我的附加隐式Projection[Specific]
,则
println(scala.reflect.runtime.universe.reify{
(new Specific).project["F"]
}.tree)
打印
App.this.ProjectSubject(new App.this.Specific()).project["F".type](App.this.Implicits.specificProjection)
所以选择的是CCD_ 25。
Scala 2.13.3。
https://scastie.scala-lang.org/Ts9UOx0aSfWuQJNOoVnSAA
原始反变体Projection
(没有类型Const
(和第一个ProjectSubject
(具有非存在Projection
(的行为是相同的。
(我在scala 2.13中的答案是,如何隐式使用[value singleton type]?可能是相关的。(
顺便说一句,对于不变的Projection
和ProjectionAvailable
,我不必对隐词进行优先级排序,并且选择了Projection[Specific]
。
https://scastie.scala-lang.org/nePYqjKGSWm8IRGLmYCwAA
当我没有定义额外的隐含Projection[Specific]
时,你用存在Projection
的方法似乎有效,选择了adapterProjection
。这种行为怎么了?
https://scastie.scala-lang.org/GiLoerYgT0OtxKyechezvA