简体   繁体   English

如何在隐式中实现使用站点差异?

[英]How to implement usage site variance in implicits?

Large redaction of the original question: now I present the whole code upfront, without showing the variants explaining my motivation.对原始问题的大量修改:现在我预先呈现整个代码,而不显示解释我动机的变体。 Apologies for the confusion.为混乱道歉。

I need a simple type class implementing a projection on one of type's member types - for the purpose of this example lets make it a straightforward cast:我需要一个简单的类型 class 在类型的成员类型之一上实现投影 - 出于本示例的目的,让它成为一个简单的转换:

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"]

In the example above, it is clear that the projection should be recursive, with Subject subclasses declaring their own rules.在上面的例子中,很明显投影应该是递归的, Subject子类声明它们自己的规则。 Obviously, I'd like the projection to be contravariant in effect:显然,我希望投影实际上是逆变的:

class Specific extends Adapter[Box["E"]]
val spec = (new Specific).project["F"] //doesn't compile

If Specific does not provide its own projection, the one for Adapter should be used, with the last expression evaluating to Adapter[Box["F"]] .如果Specific没有提供自己的投影,则应使用Adapter的投影,最后一个表达式计算为Adapter[Box["F"]] This works nicely if I declaer Projection[-S <: Subject] , but the problem is that I need the projections to preserve some properties, here expressed as the Const member type:如果我声明Projection[-S <: Subject]这很好用,但问题是我需要投影来保留一些属性,这里表示为Const成员类型:

class Projection[S <: Subject] { 
    type Project[X] <: Subject { type E = X; type Const = S#Const }
}

I dropped this constraint from the code above for clarity, as it doesn't contribute to the problem.为了清楚起见,我从上面的代码中删除了这个约束,因为它不会导致问题。

In the previous example, the compiler will complain about the lack of an implicit Projection[Specific] , without trying to upcast the value.在前面的示例中,编译器会抱怨缺少隐式Projection[Specific] ,而不会尝试向上转换值。 How to make it compile with usage site variance?如何使其与使用站点差异编译?

Not with existentials:不是存在主义:

implicit class ProjectSubject[S <: Subject](private val self :S) extends AnyVal {
    def project[X](implicit p :Projection[_ >: S <: Subject]) = ???
}

My guess was that the wildcard here is equivalent to Subject and no implicits other than Projection[Subject] will be searched for from the compiler -Xlog-implicits logs of the unabridged problem (which had a large Subject hierarchy with more implicit projection declarations).我的猜测是,这里的通配符等同于Subject并且不会从编译器-Xlog-implicits未删节问题的日志中搜索除Projection[Subject]之外的任何隐含内容(该问题具有较大的 Subject 层次结构和更多隐含的投影声明)。

I then tried the trick with an intermediate contravariant implicit, which sometimes works:然后我尝试了一个中间逆变隐式的技巧,它有时有效:

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]]
}

This looked promising, but unfortunately the compiler goes about this exactly as before: looks at the available implicit, instantiates type parameters as ProjectionAvailable[Specific, T] and complains for the lack of Projection , without taking advantage of its contravariance.这看起来很有希望,但不幸的是,编译器完全按照以前的方式处理:查看可用的隐式,将类型参数实例化为ProjectionAvailable[Specific, T]并抱怨缺少Projection ,而没有利用其逆变性。 I tried a variant with我尝试了一个变体

class ProjectionAvailable[S <: T, T <: Subject]

without any real difference apart for a more clear error.除了更明显的错误之外,没有任何真正的区别。 I tried integrating the ProjectionAvailable into Projection , but it also changed nothing:我尝试将ProjectionAvailable集成到Projection中,但它也没有改变:

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

My hunch is that it probably is doable, but requires crafty guiding the compiler by hand in type inference and for now I am out of new avenues to explore.我的预感是它可能是可行的,但需要巧妙地手动引导编译器进行类型推断,现在我没有新的探索途径。

I can't reproduce behavior you mentioned (that's why I asked in comments how you test that def project[X](implicit p:Projection[_ >: S <: Subject]) =??? or approach with ProjectionAvailable do not work for you).我无法重现您提到的行为(这就是为什么我在评论中询问您如何测试def project[X](implicit p:Projection[_ >: S <: Subject]) =???或使用ProjectionAvailable的方法不起作用为你)。

With your approach with existential Projection in ProjectSubject I additionally defined Projection[Specific] and the code doesn't compile with error使用您在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"]

so the implicit for Projection[Specific] is among candidates and I can't see how the following can be true所以Projection[Specific]的隐含在候选者中,我看不出以下内容如何为真

The wildcard here is equivalent to Subject and no implicits other than Projection[Subject] will be searched for.这里的通配符等同于Subject并且不会搜索除Projection[Subject]之外的任何隐式。

If I make adapterProjection of lower priority than my additional implicit Projection[Specific] then如果我让adapterProjection的优先级低于我的附加隐式Projection[Specific]那么

println(scala.reflect.runtime.universe.reify{
  (new Specific).project["F"]
}.tree)

prints印刷

App.this.ProjectSubject(new App.this.Specific()).project["F".type](App.this.Implicits.specificProjection)

so it's Projection[Specific] that is selected.所以它是Projection[Specific]被选中。

Scala 2.13.3. Scala 2.13.3。

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

Behavior for your original contravariant Projection (without type type Const ) and the first ProjectSubject (with non-existential Projection ) is the same.原始逆变Projection (没有类型类型Const )和第一个ProjectSubject (不存在Projection )的行为是相同的。

(My answer in In scala 2.13, how to use implicitly[value singleton type]? can be relevant.) (我在 scala 2.13 中的回答,如何隐式使用[值 singleton 类型]?可能是相关的。)

By the way, with invariant Projection and ProjectionAvailable I don't have to prioritize implicits and Projection[Specific] is selected.顺便说一句,使用不变的ProjectionProjectionAvailable我不必优先考虑隐式并选择Projection[Specific]

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

And when I don't define additional implicit Projection[Specific] your approach with existential Projection seems to work, adapterProjection is selected.而且,当我没有定义额外的隐式Projection[Specific]时,您使用存在Projection的方法似乎有效,则选择了adapterProjection What's wrong with this behavior?这种行为有什么问题?

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM