Scala中的协方差和类型推断

jde*_*lop 0 scala type-inference covariance

鉴于代码

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]

}
Run Code Online (Sandbox Code Playgroud)

我为它的类型参数设置了渲染特征协变,所以如果

RenderImages <: BasicRender 
Run Code Online (Sandbox Code Playgroud)

然后

ImagesRenderer <: Render[RenderImages]
Run Code Online (Sandbox Code Playgroud)

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

Some(new ImagesRenderer(ctx.asInstanceOf[ImagesContext]).asInstanceOf[Render[T]])
Run Code Online (Sandbox Code Playgroud)

我的理由在这里出了什么问题?

Rég*_*les 6

正如丹尼尔·C.索布拉尔解释,在这里你的问题是,你是动态的实例化不同的渲染器,在不类型系统捕捉之间的关系的方式ctx和结果类型rendererFor.解决此类问题的一种常见方法是使用类型类:

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 )
  }
}
Run Code Online (Sandbox Code Playgroud)

您可以轻松地在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]
Run Code Online (Sandbox Code Playgroud)


Dan*_*ral 5

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

现在,if你放在那里的条件可能会阻止这种情况发生,但编译器没有使用它来确定你是否返回了正确的类型.