简体   繁体   中英

Covariance and type inference in Scala

Given the code

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]

}

I made Render trait covariant for it's type parameter, so if

RenderImages <: BasicRender 

then

ImagesRenderer <: Render[RenderImages]

But it looks like the compiler is not able to infer the type of renderer in rendererFor , so I need to add explicit class casting like

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

what is wrong with my reasoning here?

As explained by Daniel C. Sobral, your problem here is that you are instantiating the different renderers dynamically, in a way that does not capture in the type system the relation between ctx and the result type of rendererFor . One common way to solve this kind of issues is to use a type class :

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

You can easily check in the REPL that the correct types are returned:

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]

T is not to be inferred: it is a parameter that is passed to the method, and there isn't really any guarantee that what is being returned is an Option[Render[T]] . For instance, assume you pass RenderImages and it returns a VideoRenderer , then it would obviously be wrong.

Now, the if conditionals you put in there might prevent that from happening, but none of that is used by the compiler to figure out whether you are returning the right type or not.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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