【问题标题】:DSL for safe navigation operator in ScalaScala 中用于安全导航运算符的 DSL
【发布时间】:2013-07-10 09:52:11
【问题描述】:

我想构建一个 Scala DSL 以将现有的 Java POJO 结构转换为等效于 Map 的结构。

但是传入的对象结构很可能包含很多空引用,这将导致输出映射中没有值。

在这种情况下,性能非常重要,因此我需要避免反射和抛出/捕获 NPE。

我已经考虑过this topic 不符合我的要求。

我认为答案可能在于使用宏来生成一些特殊类型,但我没有使用 scala 宏的经验。

更正式:

项目提供的 POJO 类:(大约有 50 个 POJO,嵌套,所以我想要一个不需要为每个类手动编写和维护类或特征的解决方案)

  case class Level1(
    @BeanProperty var a: String,
    @BeanProperty var b: Int)

  case class Level2(
    @BeanProperty var p: Level1,
    @BeanProperty var b: Int)

预期行为:

  println(convert(null)) // == Map()
  println(convert(Level2(null, 3))) // == Map("l2.b" -> 3)
  println(convert(Level2(Level1("a", 2), 3))) // == Map(l2.p.a -> a, l2.p.b -> 2, l2.b -> 3)

正确的实现,但我想要一个更简单的 DSL 来编写映射

  implicit def toOptionBuilder[T](f: => T) = new {
    def ? : Option[T] = Option(f)
  }

 def convert(l2: Level2): Map[String, _] = l2? match {
    case None => Map()
    case Some(o2) => convert(o2.p, "l2.p.") + ("l2.b" -> o2.b)
  }

  def convert(l1: Level1, prefix: String = ""): Map[String, _] = l1? match {
    case None => Map()
    case Some(o1) => Map(
      prefix + "a" -> o1.a,
      prefix + "b" -> o1.b)
  }

这是我想用 DSL 编写的方式:

 def convertDsl(l2:Level2)={
      Map(
          "l2.b" -> l2?.b,
          "l2.p.a" -> l2?.l1?.a,
          "l2.p.b" -> l2?.l1?.b
          )
    }

请注意,我可以使用“?”指定该属性是可选的。 我想要的是使用宏静态生成方法 l2.?l1 或 l2?.l1 返回 Option[Level1] (因此类型检查在我的 DSL 中正确完成)。

【问题讨论】:

标签: scala dsl scala-macros


【解决方案1】:

我无法将其精确到您上面给出的语法,但一般来说,这样的事情可能会起作用:

sealed trait FieldSpec

sealed trait ValueFieldSpec[+T] extends FieldSpec
{
  def value: Option[T]
}

case class IntFieldSpec(value: Option[Int]) extends ValueFieldSpec[Int]
case class StringFieldSpec(value: Option[String]) extends ValueFieldSpec[String]

case class Level1FieldSpec(input: Option[Level1]) extends FieldSpec
{
  def a: ValueFieldSpec[_] = StringFieldSpec(input.map(_.a))
  def b: ValueFieldSpec[_] = IntFieldSpec(input.map(_.b))
}

case class Level2FieldSpec(input: Option[Level2]) extends FieldSpec
{
  def b: ValueFieldSpec[_] = IntFieldSpec(input.map(_.b))
  def l1 = Level1FieldSpec(input.map(_.p))
}

case class SpecArrowAssoc(str: String)
{
  def ->(value: ValueFieldSpec[_]) = (str, value)
}

implicit def str2SpecArrowAssoc(str: String) = SpecArrowAssoc(str)

implicit def Level2ToFieldSpec(input: Option[Level2]) = Level2FieldSpec(input)

def map(fields: (String, ValueFieldSpec[_])*): Map[String, _] =
  Map[String, Any]((for {
    field <- fields
    value <- field._2.value
  } yield (field._1, value)):_*)

def convertDsl(implicit l2: Level2): Map[String, _] =
{
  map(
    "l2.b" -> l2.?.b,
    "l2.p.a" -> l2.?.l1.a,
    "l2.p.b" -> l2.?.l1.b
  )
}

然后我们得到:

scala> val myL2 = Level2(Level1("a", 2), 3)
myL2: Level2 = Level2(Level1(a,2),3)

scala> convertDsl(myL2)
res0: scala.collection.immutable.Map[String,Any] = Map(l2.b -> 3, l2.p.a -> a, l2.p.b -> 2)

请注意,DSL 使用“.?”而不仅仅是“?”作为我可以看到 Scala 在分号推断和后缀运算符方面的问题的唯一方法(例如,参见 @0__ 对 scala syntactic suger question 的回答)。

此外,您可以提供的字符串是任意的(不对其进行检查或解析),并且这种简单的“FieldSpec”层次结构将假定您的所有 POJO 使用“a”表示字符串字段,使用“b”表示 Int 字段等等

我相信这可以改进。

【讨论】:

  • 感谢您的回答,但这不是我想要的方向:所有 FooSpec 类都需要大量手写和维护(我将有 50 个 POJO 之类的东西要转换)。
  • 很可能@MichelDaviot,我自己也没有玩过宏,但我想知道通过宏实现相同结果的代码是否比通过多于。您还可以考虑将 Level1 和 Level2(等)FieldSpec 类中的单独方法拆分为它们自己的特征,这样您就可以从处理特定“值”字段的特征中组合特定 POJO 的处理程序。那么引入一个新的 POJO 应该不会是太大的负担。
猜你喜欢
  • 2012-03-13
  • 2016-04-07
  • 2011-05-05
  • 2016-04-08
  • 1970-01-01
  • 2017-05-27
  • 2017-12-22
  • 1970-01-01
  • 2011-09-30
相关资源
最近更新 更多