Question

I'm working on a content rendering system in Scala which renders pages composed of a bunch of individual components. I want to try to separate the mechanics of rendering my pages from the specific output the rendering process produces, and same on the level of a component.

My problem is that I can't seem to find a way to make Scala know that once instantiated, the output type of each component and the overall page that includes them match. It can't seem to infer that the ComponentRenderer and PageRenderer are both set to output String. By the error, I can tell that it knows the PageRenderer is supposed to produce a String, but since I've defined my method as taking an unspecialized ComponentRenderer, it doesn't know that render on it will produce String output.

I feel like I need to somehow mark within PageRenderer that the ComponentRenderer it will have will definitely have a matching OutputType concrete type definition, but I can't figure out how to do this declaratively.

Here's some sample code. I've tried to abbreviate it as much as possible to make the background of the problem clearer, at the cost of not actually being compilable.

// 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)
}
Was it helpful?

Solution 2

Updated answer

I found an even simpler way, which doesn't rely on structural types. Now, a PageRenderer implementation gets its OutputType from the ComponentRenderer it's defined with.

trait PageRenderer {    
  type MatchingComponentRenderer <: ComponentRenderer

  type OutputType = MatchingComponentRender#OutputType

  // ...

Old answer

I eventually solved the problem with a fix inspired by wingedsubmariner's suggestion, but sticking with abstract type members instead of converting to type parameters. I did something like this:

trait PageRenderer {
  self =>

  type OutputType

  type MatchingComponentRenderer = ComponentRenderer { type OutputType = self.OutputType }

  // ...

Then, I changed all references of ComponentRenderer to MatchedComponentRenderer within PageRenderer and its overrides. It compiled just fine after that! The structural type annotation effectively unified the OutputType.

OTHER TIPS

You should use type parameters instead:

trait ComponentRenderer[OutputType]

...

def renderComponent(component: ComponentRenderer[OutputType])

Using type members is possible but much uglier. Because you use OutputType as the type member in both PageRenderer and ComponentRenderer, we will need to alias this in PageRenderer:

trait PageRenderer {
  self =>

  def renderComponent(component: ComponentRenderer { type OutputType = self.OutputType })
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top