2

对原始问题的大量修改:现在我预先呈现整个代码,而不显示解释我动机的变体。为混乱道歉。

我需要一个简单的类型类在类型的成员类型之一上实现投影 - 出于本示例的目的,我们让它成为一个简单的转换:

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则应使用 for ,最后一个表达式评估为Adapter[Box["F"]]。如果我 declaer 这很好用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并且没有隐含,除了Projection[Subject]将从未-Xlog-implicits删节问题的编译器日志中搜索(它具有较大的主题层次结构和更多隐含的投影声明)。

然后我尝试了一个中间逆变隐式的技巧,它有时有效:

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]

除了更明显的错误之外,没有任何真正的区别。我尝试集成ProjectionAvailableinto Projection,但它也没有改变:

class Projection[-S <: T, T] { /* as before */ }
 

我的预感是它可能是可行的,但需要巧妙地手动引导编译器进行类型推断,现在我没有新的探索途径。

4

1 回答 1

0

我无法重现您提到的行为(这就是为什么我在评论中询问您如何测试该行为def project[X](implicit p :Projection[_ >: S <: Subject]) = ???或方法ProjectionAvailable不适合您)。

使用我另外定义的存在主义方法,Projection并且代码不会因错误而编译ProjectSubjectProjection[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"]

所以隐含的 forProjection[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)

所以它Projection[Specific]被选中了。

斯卡拉 2.13.3。

https://scastie.scala-lang.org/Ts9UOx0aSfWuQJNOoVnSAA

原始逆变Projection器(没有 type 类型Const)和第一个逆变器(不ProjectSubject存在Projection)的行为是相同的。

(我在 scala 2.13 中的回答,如何隐式使用[value singleton type]?可能是相关的。)

顺便说一句,有了不变量ProjectionProjectionAvailable我就不必优先考虑隐含并被Projection[Specific]选中。

https://scastie.scala-lang.org/nePYqjKGSWm8IRGLmYCwAA

当我没有定义额外的隐式时Projection[Specific],你的存在主义方法Projection似乎有效,adapterProjection被选中。这种行为有什么问题?

https://scastie.scala-lang.org/GiLoerYgT0OtxKyechezvA

于 2020-07-19T18:59:06.447 回答