Covariance and type inference in Scala -
given 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 it's type parameter, if
renderimages <: basicrender
then
imagesrenderer <: render[renderimages]
but looks compiler not able infer type of renderer in rendererfor, need add explicit class casting like
some(new imagesrenderer(ctx.asinstanceof[imagescontext]).asinstanceof[render[t]])
what wrong reasoning here?
as explained daniel c. sobral, problem here instantiating different renderers dynamically, in way not capture in type system relation between ctx
, result type of rendererfor
. 1 common way solve kind of issues use 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 check in repl correct types 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]
Comments
Post a Comment