2013-12-19 16 views
2

我想用Scala宏創建一個單參數copy方法的case class map,每種方法接受一個Play Json JsValue和一個case類實例,並返回該實例的更新副本。但是,我遇到了返回函數對象的宏語法問題。如何使用Scala宏創建一個函數對象(創建一個Map [String,(T)=> T])

給定的情況下,類

case class Clazz(id: Int, str: String, strOpt: Option[String]) 

的目的是建立地圖類的複製方法的

implicit def jsonToInt(json: JsValue) = json.as[Int] 
implicit def jsonToStr(json: JsValue) = json.as[String] 
implicit def jsonToStrOpt(json: JsValue) = json.asOpt[String] 

Map("id" -> (json: JsValue, clazz: Clazz) = clazz.copy(id = json), 
    "str" -> (json: JsValue, clazz: Clazz) = clazz.copy(str = json), ...) 

我已經發現了兩個相關的問題:

使用宏創建案例類實地圖:Scala Macros: Making a Map out of fields of a class in Scala

訪問cas使用電子類複製方法的宏:Howto model named parameters in method invocations with Scala macros?

...但我停留在如何創建一個函數對象,這樣我可以返回Map[String, (JsValue, T) => T]


編輯:感謝尤金Burmako的建議,使用quasiquotes - 這是我目前正在使用Scala 2.11.0-M7,基於我的代碼Jonathan Chow's post(我從使用(T,JsValue)=> T切換到(T,String)=> T來簡化我的REPL進口)

編輯2:現在結合$ tpe拼接

import scala.language.experimental.macros 

implicit def strToInt(str: String) = str.toInt 

def copyMapImpl[T: c.WeakTypeTag](c: scala.reflect.macros.Context): 
    c.Expr[Map[String, (T, String) => T]] = { 

    import c.universe._ 

    val tpe = weakTypeOf[T] 

    val fields = tpe.declarations.collectFirst { 
    case m: MethodSymbol if m.isPrimaryConstructor => m 
    }.get.paramss.head 

    val methods = fields.map { field => { 
    val name = field.name 
    val decoded = name.decoded 
    q"{$decoded -> {(t: $tpe, str: String) => t.copy($name = str)}}" 
    }} 

    c.Expr[Map[Sring, (T, String) => T]] { 
    q"Map(..$methods)" 
    } 
} 

def copyMap[T]: Map[String, (T, String) => T] = macro copyMapImpl[T] 

case class Clazz(i: Int, s: String) 

copyMap[Clazz] 
+0

我想知道是否有一種更簡單的方法可以在不使用宏的情況下完成所要完成的任務。你是否試圖採用'JsValue'並將其附加到'Clazz'的現有實例?即(概念上):'Json.obj(「id」 - > newId)++實例'應該等於'instance.copy(id = newId)' – ggreiner

+0

@ggreiner是的,當前代碼從數據庫中檢索一個'Clazz'實例(在MySql之上的Slick),將它分解爲json,更新json,將其轉換回更新的'Clazz'實例,並用新實例更新數據庫。我們正在探索宏作爲創建'Map [String,CopyFun]'的一種方式的原因是我們想要將json處理與數據庫層分離 –

+0

您是否考慮過使用quasiquotes? –

回答

4

除了事實上你需要將T拼接成一個quasiquote,即寫入$tpe而不是僅僅寫入T之外,你的代碼幾乎完全正確。爲了看起來更自然,我通常在宏中顯式聲明類型標記證據,例如, def foo[T](c: Context)(implicit T: c.WeakTypeTag[T]) = ...。之後,我只寫$T,它看起來幾乎沒有問題:)

您可能會問,爲什麼quasiquotes不能只知道他們寫的地方T引用宏的類型參數,然後自動拼接它。實際上,這將是非常合理的問題。在Racket和Scheme等語言中,quasiquotes足夠聰明,可以記住關於它們所編寫的詞彙上下文的事情,但是在Scala中這比較困難,因爲語言中有很多不同的範圍。然而,還有一個計劃要實現,並且已經在進行這方面的研究:https://groups.google.com/forum/#!topic/scala-language/7h27npd1DKI

相關問題