scala - 自动案例类映射

标签 scala macros

我正在使用 Play 和 Slick 构建一个 Web 应用程序,发现自己面临的情况是面向用户的表单类似,但与数据库模型并不完全相同。

因此,我有两个非常相似的案例类,并且需要从一个映射到另一个(例如,在填写表单以呈现“更新” View 时)。

在我感兴趣的案例中,数据库模型案例类是案例类形式的超集,即两者之间的唯一区别是数据库模型还有两个字段(基本上是两个标识符) .

我现在想知道是否有一种方法可以构建一个小型库(例如宏驱动)来根据成员名称自动从数据库案例类填充表单案例类。我已经发现可以使用 Paranamer 通过反射来访问此类信息,但我不想冒险这样做。

最佳答案

这是一个使用Dynamic的解决方案,因为我想尝试一下。宏将静态地决定是否发出源值方法、默认值方法的应用,或者仅提供文字。语法可能类似于 newFrom[C](k)。 (更新:请参阅下面的宏。)

import scala.language.dynamics
trait Invocable extends Dynamic {
  import scala.reflect.runtime.currentMirror
  import scala.reflect.runtime.universe._

  def applyDynamic(method: String)(source: Any) = {
    require(method endsWith "From")
    def caseMethod(s: Symbol) = s.asTerm.isCaseAccessor && s.asTerm.isMethod
    val sm = currentMirror reflect source
    val ms = sm.symbol.asClass.typeSignature.members filter caseMethod map (_.asMethod)
    val values = ms map (m => (m.name, (sm reflectMethod m)()))
    val im = currentMirror reflect this
    invokeWith(im, method dropRight 4, values.toMap)
  }

  def invokeWith(im: InstanceMirror, name: String, values: Map[Name, Any]): Any = {
    val at = TermName(name)
    val ts = im.symbol.typeSignature
    val method = (ts member at).asMethod

    // supplied value or defarg or default val for type of p
    def valueFor(p: Symbol, i: Int): Any = {
      if (values contains p.name) values(p.name)
      else ts member TermName(s"$name$$default$$${i+1}") match {
        case NoSymbol =>
          if (p.typeSignature.typeSymbol.asClass.isPrimitive) {
            if (p.typeSignature <:< typeOf[Int]) 0
            else if (p.typeSignature <:< typeOf[Double]) 0.0
            else ???
          } else null
        case defarg   => (im reflectMethod defarg.asMethod)()
      }
    }
    val args = (for (ps <- method.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
    (im reflectMethod method)(args: _*)
  }
}
case class C(a: String, b: Int, c: Double = 2.0, d: Double)
case class K(b: Int, e: String, a: String)
object C extends Invocable
object Test extends App {
  val res = C applyFrom K(8, "oh", "kay")
  Console println res      // C(kay,8,2.0,0.0)
}

更新:这是宏版本,更多的是为了乐趣而不是为了利润:

import scala.language.experimental.macros
import scala.reflect.macros._
import scala.collection.mutable.ListBuffer

def newFrom[A, B](source: A): B = macro newFrom_[A, B]

def newFrom_[A: c.WeakTypeTag, B: c.WeakTypeTag](c: Context)(source: c.Expr[A]): c.Expr[B] = { 
  import c.{ literal, literalNull } 
  import c.universe._
  import treeBuild._
  import nme.{ CONSTRUCTOR => Ctor } 

  def caseMethod(s: Symbol) = s.asTerm.isCaseAccessor && s.asTerm.isMethod
  def defaulter(name: Name, i: Int): String = s"${name.encoded}$$default$$${i+1}"
  val noargs = List[c.Tree]()

  // side effects: first evaluate the arg
  val side = ListBuffer[c.Tree]()
  val src = TermName(c freshName "src$")
  side += ValDef(Modifiers(), src, TypeTree(source.tree.tpe), source.tree)

  // take the arg as instance of a case class and use the case members
  val a = implicitly[c.WeakTypeTag[A]].tpe
  val srcs = (a.members filter caseMethod map (m => (m.name, m.asMethod))).toMap

  // construct the target, using src fields, defaults (from the companion), or zero
  val b = implicitly[c.WeakTypeTag[B]].tpe
  val bm = b.typeSymbol.asClass.companionSymbol.asModule
  val bc = bm.moduleClass.asClass.typeSignature
  val ps = (b declaration Ctor).asMethod.paramss.flatten.zipWithIndex
  val args: List[c.Tree] = ps map { case (p, i) =>
    if (srcs contains p.name)
      Select(Ident(src), p.name)
    else bc member TermName(defaulter(Ctor, i)) match { 
      case NoSymbol =>
        if (p.typeSignature.typeSymbol.asClass.isPrimitive) { 
          if (p.typeSignature <:< typeOf[Int]) literal(0).tree
          else if (p.typeSignature <:< typeOf[Double]) literal(0.0).tree
          else ???
        } else literalNull.tree
      case defarg   => Select(mkAttributedRef(bm), defarg.name)
    } 
  } 
  c.Expr(Block(side.toList, Apply(Select(New(mkAttributedIdent(b.typeSymbol)), Ctor), args)))
} 

使用情况:

case class C(a: String, b: Int, c: Double = 2.0, d: Double)
case class K(b: Int, e: String, a: String) { def i() = b }
val res = newFrom[K, C](K(8, "oh", "kay"))

关于scala - 自动案例类映射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17543403/

相关文章:

scala - 类型推断有多昂贵?

java - Scala 类文件与 Java 类文件

Scala 列表 : Why does this List operation work?

c++ - 可变参数宏 : cannot pass objects of non-trivially-copyable type through '...'

scala - 命令行参数不适用于 sbt 程序集 jar

javascript - 如何将参数从 scala.html 传递到 play 框架中的 javascript?

scala - 我在哪里可以了解如何为 Scala 宏构建 AST?

c++ - 传递常规和右值引用参数的通用宏代码

c - 如何使用宏或其他机制强制执行 API 定义?

netbeans - 在 Netbeans 中搜索和替换宏