PS: haven't written blog for a long time, 1 is not to write, 2 is not what dry. Recently accumulated some things, can be taken out of the sun. Ha ha.
First of all, boss let me simplify the code of case class copy to make it easy to read.
Case Class A (a:string,b:int) Cases Class B (a:a,b:int,c:string) Val B = B (A ("a", 2), 3, "C") B.copy (A.copy (b=2))//above is a simple example, If the case class is multiple nested, it produces a similar//a.copy (B.copy (c.copy (D.copy ...). Extra-long code.
At that time, in order to solve it, searched a lot, ' scala dynamic ' and so on, still did not find the ideal solution, as for the ' macro ', under the pressure and difficulty too much, had to use
Case Class A (A:string,b:int) Case Class B (a:a,b:int,c:string) {def aa=a.a def aa_= (value:string) =this.copy (A.copy (a=v alue))}
This is a relatively lame solution. After that, it's always been a bit of a depression, so the plan is unacceptable, especially knowing that it macro
can be solved in a more elegant and elegant way.
So, the road to the toss began. Here is a list of some of the most useful information that individuals consider:
Macro Official Documents
Exploring Scala Macros:map to case Class Conversion
Scala Macros:let our Powers combine!
Learning Scala Macros
Adding Reflection to Scala Macros
Git:underscoreio/essential-macros
Stackoverflow:where can I learn about constructing AST's for Scala macros?
Every contact with a new thing, the most troublesome is the start, is scala macro
no exception, the light to create an idea project outside the chain another project is enough, do not support the simultaneous editing of multiple projects in the same directory, now idea out of 14, solve the problem. Here is a list of the BUILD.SBT for the next two items.
Coreorganization: = "Timzaak" Name: = "core" version: = "0.1-snapshot" scalaversion: = "2.11.4" lazy val macrolib = rootproj ECT (File (".. /macrolib ")) lazy val core = project.in (File (". ")). Aggregate (Macrolib). DependsOn (Macrolib)
Macro liborganization: = "Timzaak" Name: = "Macrolib" version: = "1.0.1" scalaversion: = "2.11.4" librarydependencies ++= S EQ ("Org.scala-lang"% "scala-reflect"% Scalaversion.value, "Org.scala-lang"% "Scala-compiler"% scalaversion.val Ue
After the project is built, is Hello World, here is not detailed, interested, click here!
Well, now that the information is over and the project has Hello World, let's start solving the problem. At first, I set the DSL to
Case Class A (A:string,b:int) Case Class B (a:a,b:int,c:string) Val B = B (A ("a", 2), 3, "C") Copy (b.a.a= "New String")//Return B (A (" New String ", 2), 3," C ")
but found that the error. The beginning of knowledgemacro
It's not as powerful as I think, it's not going to change semantics directly, it should be used to generate code in batches and reduce manual duplication of code. Or maybe it's translated into宏
reason for it.
Well, let's take a step-by-step. First fix how to builda.copy(b.copy(...
the problem.
If you want to solve him, you need to know what AST Zhang is. We use the idea provided by worksheet to get it done.
Import Reflect.runtime.universe._case class C (c:string) Case Class A (A:INT,B:STRING,C:C) Val a = A (1, "", C ("")) Showraw ( Reify{a.copy (a=2)}.tree)//apply (select (Ident (Termname ("a$a ...
However, it can only provide us with a reference, or there will be some problems. Learning Scala Macrosprovides a solution. We can use it.
Get AST, the rest is to construct the target code based on the AST and requirements.
just started to construct
Case Class A (A:string,b:int)//case Class B (a:a,b:int,c:string)//val B = B (A ("a", 2), 3, "C")//copy (b.a.a= "New String")/ /--code to construct val $temp = b.a.copy (a= "New String") Val result = B.copy (a= $temp) result
but found, too hard to write,上一行的代码被下一行代码使用,并且需要创建临时变量
, and then instead of the recursive notation, remove the temporary variable.
B.copy (A.copy (a= "New String"))
Object casecopy { def copy (a: any, b:any ) = macro imp def imp (C: context) (A: c.expr[any], b: c.expr[any]) = { import c.universe._ def reverpath (V: c.Tree, lis: list[(c.tree, string)]: list[(c.tree, string)] = { v match { case [email Protected] (Termname (name)) => (tag, Name) :: lis case [email protected] (SE, termname (t)) => reverpath (se, ( tag, t) :: lis) case [email protected] (TypeName (name)) => (Thi, name) :: lis case apply (a,_) => reverpath (A,lis) case block (List (b), _) = reverpath (B,lis) case _ => c.abort ( v.pos, "only support case copy ") } } val (Path, parm) = reverpath (A.tree, nil). tail.unzip (Path.init zip parm.tail). Reverse.foldleft (q "$b": Tree) { case (re, (p, m)) => q "$p. Copy (${termname (m)}= $re) "    }  }}
run and test the code:
Case Class B (i:int) Case class ABC (A:int, B:b) object CaseC extends App {import tim.casecopy.CaseCopy.copy val abc = a BC (1, B (2)) println (copy (ABC.A, 123))}
the output is unexpectedly ()
. Look at one side of the code, only to find that the return value is not set, immediately add.
... def Copy[t] (A:any, b:any): T = Macro Imp[t] def Imp[t] (C:context) (A:c.expr[any], B:c.expr[any]): c.expr[t] = {... val re= (path.init zip parm.tail). Reverse.foldleft (q "$b": Tree) {case (re, p, m) = + q "$p. Copy (${TERMN Ame (m)}= $re) "} C.expr[t] (re) ...
Test println (Copy[abc] (ABC.A, 123))
What else is left to solve?
println(copy[ABC](abc.a,"string"))
can also be compiled by the. Type is not secure.
We add this decision to the code.
if (! ( B.actualtype<:<a.actualtype) {C.abort (b.tree.pos,s "B:${b.actualtype} must be subtype of A:${a.actualType}") }
Although only 40 lines of code, but the preparation time for more than 40 hours. This makes me miss the ability of JS dynamic generation code!
scala macro
Although the 11.x is still marked as experimental, but the official commitment in the near future will become the official library, hoping that the use of macro can be reduced by a step.
Scala macro-makes case copy easy to read