【问题标题】:How to implement usage site variance in implicits?如何在隐式中实现使用站点差异?
【发布时间】:2020-11-06 17:07:45
【问题描述】:

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

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

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 &lt;: 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]

除了更明显的错误之外没有任何真正的区别。我尝试整合 将ProjectionAvailable 转换为Projection,但它也没有任何改变:

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

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

【问题讨论】:

  • 您的代码不是独立的。 Adapter 是什么? Box 是什么?应该是implicit def adapterProjection[S &lt;: Subject](implicit p: Projection[S])...?没有 p 是隐式的,你定义了一个隐式转换,而不是类型类的实例。 val spec = new Specific.project["F"] 行无法编译。
  • 哦,抱歉,复制粘贴时好像丢失了一些行。已更新。
  • 我猜implicit def boxProjection[E]: Projection[Box[E]] { type Project[X] = Box[X] } = ???也应该被定义。
  • 您如何测试def project[X](implicit p :Projection[_ &gt;: S &lt;: Subject]) = ??? 或使用ProjectionAvailable 的方法对您不起作用?
  • 是的,这个问题中每种类型的隐式投影都被隐式假设为已定义 - 我忽略了它,因为它很明显,但事后看来我可能不应该有。我使用 Scala 2.13.2。

标签: scala implicit contravariance invariance


【解决方案1】:

我无法重现您提到的行为(这就是为什么我在 cmets 中询问您如何测试 def project[X](implicit p :Projection[_ &gt;: S &lt;: 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)

所以选择的是Projection[Specific]

Scala 2.13.3.

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

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

(我在In scala 2.13, how to use implicitly[value singleton type]? 的回答可能是相关的。)

顺便说一句,对于不变的ProjectionProjectionAvailable,我不必优先考虑隐式,并且选择了Projection[Specific]

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

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

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

【讨论】:

  • 您好,首先,感谢您投入大量时间!其次,很抱歉回复晚了——我找到了一种解决方法(不是我非常满意的一种),这不再是紧急的了。第三,我不确定我们是否在同一页面上:您是否在伴随对象中定义了您的投影(而不是,例如,在本地范围或对象投影中)?最后,对我来说,这个问题的全部意义在于没有为Specific 类型定义没有投影,希望使用为适配器定义的投影。
  • 存在主义仍然对我不起作用:我将编辑问题以提供完整的代码要点。我想逐步引入一些东西以更好地保持我的意图,但这显然适得其反。
  • @Turin 是的,完整的要点会很有用。
  • 完成。隐式使用的调试 sn-p 太棒了!我现在正在阅读您的链接。
猜你喜欢
  • 2019-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-17
  • 2021-11-03
  • 2020-11-30
  • 1970-01-01
相关资源
最近更新 更多