在Scala中协调实例之间的抽象类型成员

在Scala中协调实例之间的抽象类型成员,scala,types,Scala,Types,我正在Scala中开发一个内容呈现系统,它呈现由一组单独组件组成的页面。我想尝试将呈现页面的机制与呈现过程产生的特定输出分离,在组件级别也是如此 我的问题是,我似乎无法找到一种方法让Scala知道,一旦实例化,每个组件的输出类型与包含它们的整个页面匹配。似乎无法推断ComponentRenderer和PageRenderer都设置为输出String。通过这个错误,我可以看出它知道PageRenderer应该生成一个字符串,但是因为我已经将我的方法定义为使用一个非专用的ComponentRende

我正在Scala中开发一个内容呈现系统,它呈现由一组单独组件组成的页面。我想尝试将呈现页面的机制与呈现过程产生的特定输出分离,在组件级别也是如此

我的问题是,我似乎无法找到一种方法让Scala知道,一旦实例化,每个组件的输出类型与包含它们的整个页面匹配。似乎无法推断
ComponentRenderer
PageRenderer
都设置为输出
String
。通过这个错误,我可以看出它知道PageRenderer应该生成一个字符串,但是因为我已经将我的方法定义为使用一个非专用的ComponentRenderer,所以它不知道它上面的
render
将生成字符串输出

我觉得我需要在PageRenderer中标记,它将拥有的ComponentRenderer肯定会有一个匹配的OutputType具体类型定义,但我不知道如何以声明的方式实现这一点

下面是一些示例代码。我试图尽可能地简化它,以使问题的背景更加清晰,但代价是它实际上不可编译

// Is supposed to render a test page as a String
object Demo {
  import PageSpec._
  import Components._

  def render(page: Page): String = {
    // String-based Html renderer implementation; implements PageRenderer trait, defined below
    val renderer = new PageRenderer {
      type OutputType = String

      def renderComponent(component: ComponentRenderer): OutputType = {
        "<div class=\"component " ++ component.cssClass ++ "\">" ++ component.render ++ "</div>"
      }
      // ERROR ABOVE:-----------------------------------------------------------^    
      // found   : component.OutputType
      // required: scala.collection.GenTraversableOnce[?]

      def componentBuilder(componentDef: ComponentDef): ComponentRenderer = componentDef match {
        // We can build actual components as cases here and
        // use MockComponent as a TO-DO
        case x @ _ => new MockComponentStringRenderer {
          def componentDef = x
        }
      }
    }
    renderer.render(page)
  }
}

object PageSpec {
  trait Page {
    // some description of component configurations (ComponentDef's) within a given layout
  }
}

object Components {
  import PageSpec._

  // Abstract Component and ComponentRenderer with unfixed output types
  trait Component {
    def componentDef: ComponentDef
  }

  trait ComponentRenderer {
    this: Component =>

    type OutputType
    def cssClass: String = componentDef.id
    def render: OutputType
  }

  // Concrete component implementation capable of rendering itself as a String
  trait MockComponentStringRenderer extends ComponentRenderer with Component {
    type OutputType = String
    def render = "Component: " ++ componentDef.id
  }
}

/**
 * This is a rendering pipeline to be specialized for producing specific
 * types of output representing a Page
 */
trait PageRenderer {
  import PageSpec._
  import Components._

  type OutputType

  // Override with a function taking a ComponentRenderer and produces
  // its final output (e.g. rendered and wrapped in a div tag).
  def renderComponent(component: ComponentRenderer): OutputType

  // This turns the Page into the ComponentRenderers for output
  def buildComponentTree(page: Page): Seq[ComponentRenderer] = {
    // Something that parses a Page and returns a sequence of ComponentRenderers 
  }

  // This assembles the output of the ComponentRenderers
  def renderTree(components: Seq[ComponentRenderer]): OutputType = {

        components map { component =>
          // For each component, run the provided component wrapper, which
          // calls the callback passed to it to get the rendered contents.
          renderComponent(component)
        }
  }

  // Simply kicks off the process (does more in the full project)
  def render(page: Page): OutputType = renderTree(page)
}
//应将测试页呈现为字符串
对象演示{
导入页面规范_
导入组件_
def呈现(页面:页面):字符串={
//基于字符串的Html呈现器实现;实现PageRenderer特性,定义如下
val renderer=新页面渲染器{
类型OutputType=String
def renderComponent(组件:组件渲染器):OutputType={
“”++component.render++“
}
//上面的错误:--------------------------------------------------------------^
//找到:component.OutputType
//必需:scala.collection.GenTraversableOnce[?]
def componentBuilder(componentDef:componentDef):ComponentRenderer=componentDef匹配{
//我们可以在此处和此处构建实际组件作为案例
//将MockComponent用作待办事项
case x@u=>新的MockComponentStringRenderer{
def组件def=x
}
}
}
renderer.render(第页)
}
}
对象页面规范{
特征页{
//给定布局中组件配置(ComponentDef)的一些描述
}
}
对象组件{
导入页面规范_
//具有不固定输出类型的抽象组件和组件渲染器
性状成分{
def componentDef:componentDef
}
特征成分呈现器{
这个:组件=>
类型输出类型
def cssClass:String=componentDef.id
def render:OutputType
}
//能够将自身呈现为字符串的具体组件实现
trait MockComponentStringRenderer使用组件扩展ComponentRenderer{
类型OutputType=String
def render=“组件:”++componentDef.id
}
}
/**
*这是一个渲染管道,专门用于生成特定的
*表示页面的输出类型
*/
特征页面渲染器{
导入页面规范_
导入组件_
类型输出类型
//使用函数替代ComponentRenderer并生成
//其最终输出(例如,在div标记中呈现和包装)。
def renderComponent(组件:组件渲染器):输出类型
//这会将页面变成用于输出的组件渲染器
def buildComponentTree(第页:第页):Seq[ComponentRenderer]={
//解析页面并返回一系列组件呈现程序的东西
}
//这将组装组件渲染器的输出
def renderTree(组件:Seq[ComponentRenderer]):OutputType={
组件映射{component=>
//对于每个组件,运行提供的组件包装器,其中
//调用传递给它的回调以获取呈现的内容。
渲染组件(组件)
}
}
//简单地启动流程(在整个项目中做得更多)
def render(page:page):OutputType=renderTree(page)
}

您应该改用类型参数:

trait ComponentRenderer[OutputType]

...

def renderComponent(component: ComponentRenderer[OutputType])
使用类型成员是可能的,但要难看得多。由于您在
PageRenderer
ComponentRenderer
中都使用
OutputType
作为类型成员,因此我们需要在
PageRenderer
中别名
this

trait PageRenderer {
  self =>

  def renderComponent(component: ComponentRenderer { type OutputType = self.OutputType })

应改用类型参数:

trait ComponentRenderer[OutputType]

...

def renderComponent(component: ComponentRenderer[OutputType])
使用类型成员是可能的,但要难看得多。由于您在
PageRenderer
ComponentRenderer
中都使用
OutputType
作为类型成员,因此我们需要在
PageRenderer
中别名
this

trait PageRenderer {
  self =>

  def renderComponent(component: ComponentRenderer { type OutputType = self.OutputType })

应改用类型参数:

trait ComponentRenderer[OutputType]

...

def renderComponent(component: ComponentRenderer[OutputType])
使用类型成员是可能的,但要难看得多。由于您在
PageRenderer
ComponentRenderer
中都使用
OutputType
作为类型成员,因此我们需要在
PageRenderer
中别名
this

trait PageRenderer {
  self =>

  def renderComponent(component: ComponentRenderer { type OutputType = self.OutputType })

应改用类型参数:

trait ComponentRenderer[OutputType]

...

def renderComponent(component: ComponentRenderer[OutputType])
使用类型成员是可能的,但要难看得多。由于您在
PageRenderer
ComponentRenderer
中都使用
OutputType
作为类型成员,因此我们需要在
PageRenderer
中别名
this

trait PageRenderer {
  self =>

  def renderComponent(component: ComponentRenderer { type OutputType = self.OutputType })

更新的答案

我发现了一种更简单的方法,它不依赖于结构类型。现在,一个
PageRenderer
实现从它定义的
ComponentRenderer
中获取它的
OutputType

trait PageRenderer {    
  type MatchingComponentRenderer <: ComponentRenderer

  type OutputType = MatchingComponentRender#OutputType

  // ...

然后,我将
PageRenderer
ComponentRenderer
的所有引用都更改为
MatchedComponentRenderer
及其覆盖。在那之后一切都很好!结构类型注释有效地统一了
输出类型

更新的答案

我发现了一种更简单的方法,它不依赖于结构类型。现在,一个
PageRenderer
实现从
组件获取它的
OutputType