我做了一些研究,并且能够想出一个实现我的想法的想法,这目前是相当令人满意的(至少对我来说)。简而言之,我想维护一个递归树结构的字符串表示,用户可以就地修改它,然后能够将该表示转换为实际实例。原来类型类是我正在寻找的。我想我会继续为那些至少和我一样不熟悉使用类型类的人发布这个。如果有人知道如何消除必须为调用“构建”提供类型参数的必要性,请告诉我。另外,我希望有一个更简洁的隐式构建器函数实现,它不使用“@unchecked”注释来消除烦人的编译器警告。:-)
object Messages {
// Test classes
case class Person(name: String, age: Int, pet: Dog)
case class Dog(name: String)
case class Device(deviceType: DeviceType, on: Boolean)
sealed trait DeviceType
case class Android() extends DeviceType
case class iOS() extends DeviceType
case class Windows() extends DeviceType
// Builders...
trait Builder[A] {
def build: Node => A
}
object Builder {
def build[A](f: Node => A) = new Builder[A] {
val build = f
}
// Terminals...
implicit val intBuilder: Builder[Int] = build(node => (node: @unchecked) match {
case Term(_, value) => value.toInt
})
implicit val stringBuilder: Builder[String] = build(node => (node: @unchecked) match {
case Term(_, value) => value
})
implicit val booleanBuilder: Builder[Boolean] = build(node => (node: @unchecked) match {
case Term(_, value) => value.toBoolean
})
// Case classes (composites)
implicit val dogBuilder: Builder[Dog] = build[Dog](node => (node: @unchecked) match {
case Tree(_, children) =>
Dog(children(0).build[String])
})
implicit val personBuilder: Builder[Person] = build[Person](node => (node: @unchecked) match {
case Tree(_, children) =>
Person(children(0).build[String], children(1).build[Int], children(2).build[Dog])
})
implicit val deviceTypeBuilder: Builder[DeviceType] = build[DeviceType](node => (node: @unchecked) match {
case Term(_, value) => value match {
case "Android" => Android()
case "iOS" => iOS()
case "Windows" => Windows()
}
})
implicit val deviceBuilder: Builder[Device] = build[Device] (node => (node: @unchecked) match {
case Tree(_, children) => Device(children(0).build[DeviceType], children(1).build[Boolean])
})
}
// Data Structures...
sealed trait Node {
val name: String
def prompt: String
def build[A: Builder]: A = implicitly[Builder[A]].build(this)
}
case class Tree(name: String, children: IndexedSeq[Node]) extends Node {
override def prompt = {
val choices = children.zipWithIndex.map { case (node, idx) => s"${idx + 1}) ${node.name}" }.mkString("\n")
s"$toString\n$choices\n"
}
override def toString = name + children.mkString("(", ", ", ")")
}
case class Term(name: String, value: String) extends Node {
override def prompt = s"$name = "
override def toString = s"$name = $value"
}
def main(args: Array[String]): Unit = {
val person = Tree("Person", IndexedSeq[Node](Term("name", "Fred"), Term("age", "45"),
Tree("pet", IndexedSeq[Node](Term("name", "Fido")))))
val device = Tree("some device", IndexedSeq[Node](Term("type", "iOS"), Term("on", "true")))
println(person.prompt)
println(person.toString)
// TODO How to remove necessity of providing type parameter?
println(person.build[Person])
println(device.build[Device])
}
}