2013-05-20 3 views
0

код ссылкиковариации и тип вывода в Scala

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 признака коварианты для его параметра типа, так что если

RenderImages <: BasicRender 

затем

ImagesRenderer <: Render[RenderImages] 

Но это выглядит компилятор не может вывести тип рендерера в rendererFor, поэтому I n чтобы добавить явное классное литье, например

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

Что не так с моими рассуждениями здесь?

+0

Не вы означает «Render», где вы говорите «Renderer» выше? –

+0

@ DanielC.Sobral да, исправлено – jdevelop

ответ

5

Как объяснил Даниэль С. Собрал, ваша проблема заключается в том, что вы создаете экземпляр различных рендерингов динамически, таким образом, чтобы не фиксировать в системе типов отношение между 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) 
    } 
} 

Вы можете легко проверить в РЕПЛ, что правильные типы возвращаются:

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

T не должно быть выведено: это параметр, передаваемый методу, и на самом деле нет никакой гарантии, что возвращаемое является Option[Render[T]]. Например, предположим, что вы прошли RenderImages, и он возвращает VideoRenderer, то это, очевидно, было бы неправильно.

Теперь условия, которые вы вставляете, могут помешать этому, но это не используется компилятором, чтобы выяснить, возвращаете ли вы правильный тип или нет.

+0

Вы находитесь по причинам, почему он не компилируется. Тем не менее, я хотел бы указать, что 'manifest [T] .erasure' на самом деле правильный: манифест передается как граница контекста. –

+0

@ RégisJean-Gilles Черт, я пропустил контекст! Спасибо, что указали это. –

+0

так что это лучший способ сделать это не так? Поддерживает ли Scala зависимые типы? – jdevelop

Смежные вопросы