我正在使用Scala中的内容呈现系统,该系统呈现由一堆单独组件组成的页面。我想尝试将渲染过程产生的特定输出与渲染过程产生的特定输出分开,同样在组件层面上。
我的问题是,我似乎找不到让Scala知道的方法,一旦实例化,每个组件的输出类型和包含它们的整个页面都匹配。它似乎无法推断ComponentRenderer
和PageRenderer
都设置为输出String
。由于错误,我可以告诉它知道PageRenderer应该生成一个String,但是因为我已经将我的方法定义为使用非专业的ComponentRenderer,所以它不知道它上面有render
将产生String输出。
我觉得我需要在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)
}
答案 0 :(得分:2)
您应该使用类型参数:
trait ComponentRenderer[OutputType]
...
def renderComponent(component: ComponentRenderer[OutputType])
使用类型成员是可能的,但更加丑陋。由于您在OutputType
和PageRenderer
中都使用ComponentRenderer
作为类型成员,因此我们需要在this
中使用别名PageRenderer
:
trait PageRenderer {
self =>
def renderComponent(component: ComponentRenderer { type OutputType = self.OutputType })
答案 1 :(得分:1)
更新回答
我找到了一种更简单的方法,它不依赖于结构类型。现在,PageRenderer
实施从其定义的OutputType
获取ComponentRenderer
。
trait PageRenderer {
type MatchingComponentRenderer <: ComponentRenderer
type OutputType = MatchingComponentRender#OutputType
// ...
旧答案
我最终通过wingedsubmariner的建议启发了修复解决问题,但坚持使用抽象类型成员而不是转换为类型参数。我做了这样的事情:
trait PageRenderer {
self =>
type OutputType
type MatchingComponentRenderer = ComponentRenderer { type OutputType = self.OutputType }
// ...
然后,我在ComponentRenderer
内更改了MatchedComponentRenderer
到PageRenderer
的所有引用及其覆盖。之后它编译得很好!结构类型注释有效地统一了OutputType
。