如何在Scala中以递归方式应用隐式转换

时间:2012-12-02 17:45:35

标签: scala recursion implicits

我正在尝试编写一个转换库,用于将一些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]])或类似的......

你们可以请我给我一些关于如何实现它以及我做错了什么的建议吗?

谢谢!

2 个答案:

答案 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>

一些评论:

  • 其次,我必须提供“来自可靠”的返回类型。方法。我确信存在更好的解决方案,但我不熟悉隐式转换。
  • 最后,如果有人有建议,我会很乐意编辑我的答案。 (或随意编辑)