我正在尝试编写一个转换库,用于将一些scala类型转换为HTML表示形式。我想要比List(1,2).toHtml
执行<ul><li>1</li><li>2</li></ul>
并获取String
。
到目前为止,我已经编写了一组隐式转换,这些转换在检测结果类型和应用正确的toHtml
方面效果很好。
让我展示一个样本:
object Conversions {
implicit def fromIterable[A](l : Iterable[A]) = new Object {
def toHtml = <ul> { l.map{ e => <li>{ e }</li> } } </ul> toString
}
}
import Conversions._
使用此代码,每当我问编译器List(1,2).toHtml
时,我都会得到正确的转换。和任何其他Iterable
val一样。
我的问题和问题是如何递归地使用这个toHtml转换?因为如果我输入List( List(1,2), List(3,4) ).toHtml
我希望获得<ul> <li><ul><li>1</li><li>2</li></ul></li> <li><ul><li>3</li><li>4</li></ul></li> </ul>
,则toHtml
转换会递归地应用于输入Iterable
的每个元素。
我尝试将toHtml
定义更改为def toHtml = <ul> { l.map{ e => <li>{ e.toHtml }</li> } } </ul> toString
但这不起作用,因为编译器告诉我value toHtml is not a member of type parameter A
这是完全合理的。
我知道我的问题可能存在于new Object { ... }
我从fromIterable[A]
隐式定义返回,这可能是返回一个具有特征或类别的类。
我已经尝试了很多关于implicits的内容,但我还没有想出能够递归地应用这个toHtml
转换,而无需取消参数fromIterable签名并定义几个特定情况,如fromIterable(l : List[List[Any]])
或类似的......
你们可以请我给我一些关于如何实现它以及我做错了什么的建议吗?
谢谢!
答案 0 :(得分:7)
您可以使用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
}
这适用于任意深度的嵌套列表:
val messy = List(List(List("a")), List(List("b", "c"), List("c")))
val printer = new xml.PrettyPrinter(80, 2)
然后:
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>
有关上述方法的详细说明,请参阅我的回答here。
答案 1 :(得分:4)
让我们首先看看如果没有整个隐含/对象和转换的东西我们怎么做。我也删除了toString
,但可以随意添加它。
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>
现在让我们重新包装一切以回答你的问题:
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._
这给出了期望的结果:
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>
一些评论: