【问题标题】:Automatic case class mapping自动案例类映射
【发布时间】:2013-07-09 08:24:32
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: scala macros


    【解决方案1】:

    这是使用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"))
    

    【讨论】:

      猜你喜欢
      • 2010-11-16
      • 1970-01-01
      • 1970-01-01
      • 2021-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多