【问题标题】:Covariance and type inference in ScalaScala 中的协方差和类型推断
【发布时间】:2013-05-20 04:40:10
【问题描述】:

给定代码

object Renderer {

  sealed abstract class BasicRender

  case class RenderImages(img: Array[File]) extends BasicRender

  case class RenderVideo(video: File) extends BasicRender

  def rendererFor[T <: BasicRender : Manifest, Z <: Render.RenderingContext](ctx: Z): Option[Render[T]] = {
    val z = manifest[T].erasure
    if (z == classOf[RenderImages]) {
      Some(new ImagesRenderer(ctx.asInstanceOf[ImagesContext])) // .asInstanceOf[Render[T]])
    } else
    if (z == classOf[RenderVideo]) {
      Some(new VideoRenderer(ctx.asInstanceOf[VideoContext])) // .asInstanceOf[Render[T]])
    } else {
      None
    }
  }

  private class ImagesRenderer(ctx: ImagesContext) extends Render[RenderImages] {

    override def renderJSON(json: String)(implicit jsCtx: PhantomJsContext) = {
      None
    }

  }

  private class VideoRenderer(ctx: VideoContext) extends Render[RenderVideo] {

    override def renderJSON(json: String)(implicit jsCtx: PhantomJsContext) = {
      None
    }

  }


}

trait Render[+Out] {

  def renderJSON(json: String)(implicit jsCtx: PhantomJsContext): Option[Out]

}

我为它的类型参数设置了 Render trait 协变,所以如果

RenderImages <: BasicRender 

然后

ImagesRenderer <: Render[RenderImages]

但看起来编译器无法在 rendererFor 中推断渲染器的类型,所以我需要添加显式的类转换,如

Some(new ImagesRenderer(ctx.asInstanceOf[ImagesContext]).asInstanceOf[Render[T]])

我的推理有什么问题?

【问题讨论】:

  • 你的意思是Render 上面的Renderer 吗?
  • @DanielC.Sobral 是的,已更正

标签: scala type-inference covariance


【解决方案1】:

正如 Daniel C. Sobral 所解释的,您的问题是您正在动态地实例化不同的渲染器,其方式不会在类型系统中捕获ctxrendererFor 的结果类型之间的关系。 解决此类问题的一种常见方法是使用类型类

import java.io.File

class PhantomJsContext

trait Renderer[+Out] {
  def renderJSON(json: String)(implicit jsCtx: PhantomJsContext): Option[Out]
}

trait RendererFactory[ContextType, ResultType] {
  def buildRenderer( ctx: ContextType ): Renderer[ResultType]
}

object Renderer {
  case class RenderImages(img: Array[File])
  case class RenderVideo(video: File)

  trait ImagesContext  
  trait VideoContext

  def rendererFor[ContextType, ResultType](ctx: ContextType)( implicit factory: RendererFactory[ContextType, ResultType] ): Renderer[ResultType] = {
    factory.buildRenderer( ctx )
  }

  class ImagesRenderer(ctx: ImagesContext) extends Renderer[RenderImages] {
    def renderJSON(json: String)(implicit jsCtx: PhantomJsContext) = ???
  }
  implicit object ImagesRendererFactory extends RendererFactory[ImagesContext, RenderImages] {
    def buildRenderer( ctx: ImagesContext ) = new ImagesRenderer( ctx )
  }

  class VideoRenderer(ctx: VideoContext) extends Renderer[RenderVideo] {
    def renderJSON(json: String)(implicit jsCtx: PhantomJsContext) = ???
  }
  implicit object VideoRendererFactory extends RendererFactory[VideoContext, RenderVideo] {
    def buildRenderer( ctx: VideoContext ) = new VideoRenderer( ctx )
  }
}

您可以轻松地在 REPL 中检查是否返回了正确的类型:

scala> lazy val r1 = Renderer.rendererFor( new Renderer.ImagesContext {} )
r1: Renderer[Renderer.RenderImages] = <lazy>

scala> :type r1
Renderer[Renderer.RenderImages]

scala> lazy val r2 = Renderer.rendererFor( new Renderer.VideoContext {} )
r2: Renderer[Renderer.RenderVideo] = <lazy>

scala> :type r2
Renderer[Renderer.RenderVideo]

【讨论】:

    【解决方案2】:

    T 不能被推断:它是一个传递给方法的参数,并不能保证返回的是一个Option[Render[T]]。例如,假设你传递了RenderImages,它返回一个VideoRenderer,那么显然是错误的。

    现在,您输入的if 条件可能会阻止这种情况发生,但编译器不会使用这些条件来确定您是否返回了正确的类型。

    【讨论】:

    • 您了解它无法编译的原因。但是,我想指出 manifest[T].erasure 实际上是正确的:清单作为上下文绑定传递。
    • @RégisJean-Gilles 该死的,我错过了上下文绑定!感谢您指出。
    • 那么最好的方法是什么? Scala 是否支持依赖类型?
    • @jdevelop Scala 确实支持依赖类型,但它的编码与其他语言不同。不过,我认为 Régis answer 提出的解决方案更好。
    • 是的,我意识到如果我必须传递渲染上下文 - 那么使用 typeclass 很容易。感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多