Question

I am trying to write a conversions library for converting some scala types to an HTML representation. I would want, say, to execute List(1,2).toHtml and obtain <ul><li>1</li><li>2</li></ul>, as a String.
So far i've written a set of implicit conversions that work well on detecting the result type and applying the correct toHtml.

Let me show a sample:

object Conversions {

  implicit def fromIterable[A](l : Iterable[A]) = new Object {
       def toHtml = <ul> { l.map{ e => <li>{ e }</li> } } </ul> toString
  }
}
import Conversions._

With this code, whenever i ask the compiler List(1,2).toHtml i get the correct conversion. As it would with any other Iterable val.

My problem and question here is how can i use this toHtml conversion recursively? Because if i typed List( List(1,2), List(3,4) ).toHtml i would want to get <ul> <li><ul><li>1</li><li>2</li></ul></li> <li><ul><li>3</li><li>4</li></ul></li> </ul>, the toHtml conversion applied recursively to every element of the input Iterable.

I tried to change the toHtml definition to def toHtml = <ul> { l.map{ e => <li>{ e.toHtml }</li> } } </ul> toString which didn't work because the compiler told me value toHtml is not a member of type parameter A which makes perfect sense.
I know that my problem probably resides in the new Object { ... } i am returning from the fromIterable[A] implicit definition, which should probably be returning a class with a trait or something.
I've tried reading a lot about implicits but i haven't figured it out yet out to be able to apply recursively this toHtml conversion without de-parametizing the fromIterable signature and defining several specific cases like fromIterable(l : List[List[Any]]) or something like that...

Can you guys please me give me some advices on how to achieve it and what am i doing wrong?

Thanks!

Was it helpful?

Solution

You can solve this kind of problem in an elegant and type-safe way with type classes:

// Evidence that we can turn an A into some XML:
trait Markup[-A] { def toHtml(a: A): xml.Node }

def baseMarkup[A] = new Markup[A] { def toHtml(a: A) = xml.Text(a.toString) }

implicit def markup[A](implicit m: Markup[A] = baseMarkup[A]) =
  new Markup[Iterable[A]] {
    def toHtml(c: Iterable[A]) = <ul>
      { c.map(child => <li>{ m.toHtml(child) }</li>) }
    </ul>
  }

implicit def fromMarkup[A](a: A)(implicit m: Markup[A]) = new {
  def toHtml = m toHtml a
}

This works for nested lists of arbitrary depth:

val messy = List(List(List("a")), List(List("b", "c"), List("c")))

val printer = new xml.PrettyPrinter(80, 2)

And then:

scala> printer format messy.toHtml
res1: String = 
<ul>
  <li>
    <ul>
      <li>
        <ul>
          <li>a</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <ul>
      <li>
        <ul>
          <li>b</li>
          <li>c</li>
        </ul>
      </li>
      <li>
        <ul>
          <li>c</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

See my answer here for a more detailed explanation of the methods above.

OTHER TIPS

Let's first see how we can do it without the whole implicit/object and conversions stuff. I also removed the toString but feel free to add it on call.

def toHtml[A](l:Iterable[A]):scala.xml.Elem  = 
  <ul> {l.map( _ match{ 
    case e:Iterable[_] => <li>{toHtml(e)}</li> 
    case e => <li>{e}</li>
  })}</ul>

// Exiting paste mode, now interpreting.

toHtml: [A](l: Iterable[A])scala.xml.Elem
scala> toHtml(List(List(1,2), List(3,4)))
res17: scala.xml.Elem = <ul> <li><ul> <li>1</li><li>2</li></ul></li><li><ul> <li>3</li><li>4</li></ul></li></ul> 

Let us now re-wrap everything in order to answer your question:

object Conversions {
  implicit def fromIterable[A](l : Iterable[A]):Object{def toHtml:xml.Elem} = new Object {
    def toHtml:xml.Elem  = 
      <ul>{l.map( _ match{ 
        case e:Iterable[_] => <li>{e.toHtml}</li> 
        case e => <li>{e}</li>
      })}</ul>
  }
}
import Conversions._

Which gives the desired result:

scala> List(List(1,2), List(3,4)).toHtml
res3: scala.xml.Elem = <ul><li><ul><li>1</li><li>2</li></ul></li><li><ul><li>3</li><li>4</li></ul></li></ul>

A few remarks:

  • Second, I had to give the return type of the 'fromIterable' method. I am sure that there exists a better solution but I am not that experienced with implicit conversions.
  • Finally, I will be glad to edit my answer if anyone has suggestions. (Or feel free to edit)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top