tag:blogger.com,1999:blog-62738186848314543062024-03-18T11:48:25.542+02:00cat **/*Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.comBlogger4125tag:blogger.com,1999:blog-6273818684831454306.post-16200335429200235272012-09-11T21:41:00.001+03:002012-09-11T21:41:48.322+03:00Full (de)serialization for Play Json using general purpose macrosI've released a new version of <a href="https://github.com/akshaal/akmacros">akmacros</a> library for Scala 2.10.
The release includes a new macro called 'factory'. Thanks to the new macro it is possible to construct a class
with a public default constructor just passing a function to the generated factory of the class. The function
receives symbol of an argument of the constructor and returns value for this argument. If function returns None, then
default value for the argument is evaluated (provided that it is defined for the given argument). Here is the example:
<pre>
scala> <span style="color: #00ffff;">import</span> info.akshaal.clazz._
import info.akshaal.clazz._
scala> <span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Record</span> (<span style="color: #13ff12;">name</span>: <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">twitter</span>: <span style="color: #98fb98;">Option[String]</span> = None)
defined class Record
scala> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">recordFactory</span> = <span style="color: #24BEE3;">factory</span>[<span style="color: #98fb98;">Any</span>, <span style="color: #98fb98;">Record</span>]('castValue)
recordFactory: (Symbol => Option[Any]) => Record = <function1>
scala> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">args</span> = <span style="color: #13ff12;">Map</span>('name -> <span style="color: #ffa07a;">"Evgeny"</span>)
args: scala.collection.immutable.Map[Symbol,String] = Map('name -> Evgeny)
scala> recordFactory(args.<span style="color: #24BEE3;">get</span>)
res0: Record = Record(Evgeny,None)
</pre>
<h3>Writing less boilerplate code for Play Json</h3>
Just to demonstrate the way macro functions can simplify your code I've created <a href="https://github.com/akshaal/akmacros-json">a small project on github</a>.
The project includes Json library from the Play2.0 framework mostly as-is. The only changes I did are related to making it work on scala 2.10
(upgraded it to patched jerkson and so on).
You might be interested in <a href="https://github.com/akshaal/akmacros-json/blob/master/src/main/scala/info/akshaal/play.scala">this file</a>.
It is the only file that I implemented in that project in order to add support for macros to the Play Json.
Following the same pattern, you can easily use almost any other Json library like this.<BR/><BR/>
Lets look what it gives you without any reflections.. Lets start with something simple:
<pre>
scala> <span style="color: #00ffff;">import</span> info.akshaal.json.play._
import info.akshaal.json.play._
scala> <span style="color: #00ffff;">import</span> play.api.libs.json._
import play.api.libs.json._
scala> <span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Simple</span>(<span style="color: #13ff12;">str</span>: <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">num</span>: <span style="color: #98fb98;">Int </span>= <span style="color: #00ffff;">new</span> Random().nextInt())
defined class Simple
scala> <span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">simpleJsFactory</span> = <span style="color: #24BEE3;">factory</span>[<span style="color: #98fb98;">Simple</span>]('fromJson)
simpleJsFactory: (Symbol => Option[play.api.libs.json.JsValue]) => Simple = <function1>
scala> <span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">simpleJsFields</span> = <span style="color: #24BEE3;">allFields</span>[<span style="color: #98fb98;">Simple</span>]('jsonate)
simpleJsFields: List[info.akshaal.clazz.Field[Simple,play.api.libs.json.JsValue,None.type]] = List(Field(str,<function1>,None), Field(num,<function1>,None))
</pre>
We can serialize and then deserialize:
<pre>
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">obj</span> = <span style="color: #00ffff;">new</span> <span style="color: #98fb98;">Simple</span>(<span style="color: #ffa07a;">"123"</span>, 5)
obj: Simple = Simple(123,5)
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">objJs</span> = Json.<span style="color: #24BEE3;">toJson</span>(<span style="color: #73ff73;">obj</span>)
objJs: play.api.libs.json.JsValue = {<span style="color: #ffa07a;">"str"</span>:<span style="color: #ffa07a;">"123"</span>,<span style="color: #ffa07a;">"num"</span><span style="color: #b0c4de;">:5</span>}
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">obj2</span> = Json.<span style="color: #24BEE3;">fromJson</span>[Simple](<span style="color: #73ff73;">objJs</span>)
obj2: Simple = Simple(123,5)
</pre>
That was easy. Now lets try to leverage default value feature of the case class:
<pre>
scala> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">halfObjJs</span> = Json.<span style="color: #24BEE3;">parse</span>(<span style="color: #ffa07a;">""" {"str":"test"} """</span>)
halfObjJs: play.api.libs.json.JsValue = {<span style="color: #ffa07a;">"str"</span>:<span style="color: #ffa07a;">"test"</span>}
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">halfObj</span> = Json.<span style="color: #24BEE3;">fromJson</span>[Simple](<span style="color: #73ff73;">halfObjJs</span>)
halfObj: Simple = Simple(test,-813663852)
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">halfObj</span> = Json.<span style="color: #24BEE3;">fromJson</span>[Simple](<span style="color: #73ff73;">halfObjJs</span>)
halfObj: Simple = Simple(test,-948806581)
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">halfObj</span> = Json.<span style="color: #24BEE3;">fromJson</span>[Simple](<span style="color: #73ff73;">halfObjJs</span>)
halfObj: Simple = Simple(test,428745442)
</pre>
Note that we parsed halfObjJs three times. And 'num' was always different. That is because value for 'num' is missing in the halfObjJs and so the expression for default
value was used (which is Random().nextInt, see defintion of Simple class). How about parametrized classes? Lets try:
<pre>
scala> <span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Event</span>[<span style="color: #98fb98;">T</span>](<span style="color: #13ff12;">kind</span>: <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">payloads</span>: <span style="color: #026DF7;">T</span>)
defined class Event
scala> <span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">eventJsFields</span>[<span style="color: #98fb98;">T</span><span style="color: #084EA8;">: Writes</span><span style="color: #ffff70;">] </span>= allFields[Event[<span style="color: #ffff70;">T</span>]]('jsonate)
eventJsFields: [T](implicit evidence$1: play.api.libs.json.Writes[T])List[info.akshaal.clazz.Field[Event[T],play.api.libs.json.JsValue,None.type]]
scala> <span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">eventJsFactory</span>[<span style="color: #98fb98;">T</span><span style="color: #084EA8;">: Reads</span><span style="color: #ffff70;">] =</span> factory[Event[<span style="color: #ffff70;">T</span>]]('fromJson)
eventJsFactory: [T](implicit evidence$1: play.api.libs.json.Reads[T])(Symbol => Option[play.api.libs.json.JsValue]) => Event[T]
</pre>
And test:
<pre>
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">event</span> = <span style="color: #026DF7;">Event</span>(kind = <span style="color: #ffa07a;">"strange"</span>, payloads = <span style="color: #73ff73;">obj</span>)
event: Event[Simple] = Event(strange,Simple(123,5))
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">eventJs</span> = <span style="color: #026DF7;">Json</span>.<span style="color: #24BEE3;">toJson</span>(<span style="color: #73ff73;">event</span>)
eventJs: play.api.libs.json.JsValue = {<span style="color: #ffa07a;">"kind"</span>:<span style="color: #ffa07a;">"strange"</span>,<span style="color: #ffa07a;">"payloads"</span>:{<span style="color: #ffa07a;">"str"</span>:<span style="color: #ffa07a;">"123"</span>,<span style="color: #ffa07a;">"num"</span><span style="color: #b0c4de;">:5</span>}}
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">event3</span> = <span style="color: #026DF7;">Json</span>.<span style="color: #24BEE3;">fromJson</span>[<span style="color: #98fb98;">Event[Simple]</span>](<span style="color: #73ff73;">eventJs</span>)
event3: Event[Simple] = Event(strange,Simple(123,5))
</pre>
In the real world it is unlikely that you will use completely unrestricted types (like T in the example above) in your case classes with json.
It is more likely that there will be a (sealed) trait and some set of its subtypes. Lets see how it works. First, we will define some more case classes:
<pre>
<span style="color: #00ffff;">sealed</span> <span style="color: #00ffff;">trait</span> <span style="color: #98fb98;">Message</span>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">QuitMessage</span>(<span style="color: #13ff12;">msg</span>: <span style="color: #98fb98;">String</span>) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">Message</span>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Heartbeat</span>(<span style="color: #13ff12;">id</span>: <span style="color: #98fb98;">Int</span>) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">Message</span>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Messages</span>(<span style="color: #13ff12;">userId</span>: <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">messages</span>: <span style="color: #98fb98;">List[Message]</span> = List.empty)
</pre>
Now, lets define how to serialize our new case classes:
<pre>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">messageWrites</span> = <span style="color: #24BEE3;">matchingWrites</span>[<span style="color: #084EA8;">Message</span>] {
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">m</span>: <span style="color: #98fb98;">QuitMessage => </span><span style="color: #24BEE3;">quitMessageJsFields</span><span style="color: #98fb98;">.</span><span style="color: #24BEE3;">toWrites</span><span style="color: #98fb98;">.</span><span style="color: #24BEE3;">writes</span>(<span style="color: #eedd82;">m</span>)
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">m</span>: <span style="color: #98fb98;">Heartbeat => </span><span style="color: #24BEE3;">heartbeatJsFields</span><span style="color: #98fb98;">.</span><span style="color: #24BEE3;">toWrites</span><span style="color: #98fb98;">.</span><span style="color: #24BEE3;">writes</span>(<span style="color: #eedd82;">m</span>)
}
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">quitMessageJsFields</span> = allFields[QuitMessage]('jsonate)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">heartbeatJsFields</span> = allFields[Heartbeat]('jsonate)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">messagesJsFields</span> = allFields[Messages]('jsonate)
</pre>
And if deserialization is needed, lets define how to do it:
<pre>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">quitMessageJsFactory</span> = factory[QuitMessage]('fromJson)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">heartbeatJsFactory</span> = factory[Heartbeat]('fromJson)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">messagesJsFactory</span> = factory[Messages]('fromJson)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">messageReads</span>: <span style="color: #084EA8;">Reads[Message]</span> =
<span style="color: #24BEE3;">predicatedReads</span>[<span style="color: #084EA8;">Message</span>](
<span style="color: #24BEE3;">jsHas</span>('msg) -> <span style="color: #24BEE3;">quitMessageJsFactory</span>,
<span style="color: #24BEE3;">jsHas</span>('id) -> <span style="color: #24BEE3;">heartbeatJsFactory</span>
)
</pre>
Finally, lets try it:
<pre>
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">event5</span> =
<span style="color: #026DF7;">Event</span>(kind = <span style="color: #ffa07a;">"t1"</span>,
payloads = <span style="color: #026DF7;">Messages</span>(userId = 4, messages = List(<span style="color: #026DF7;">Heartbeat</span>(5), <span style="color: #026DF7;">QuitMessage</span>(<span style="color: #ffa07a;">"bye!"</span>))))
event5: Event[Messages] = Event(t1,Messages(4,List(Heartbeat(5), QuitMessage(bye!))))
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">event5Js</span> = <span style="color: #026DF7;">Json</span>.<span style="color: #24BEE3;">toJson</span>(<span style="color: #73ff73;">event5</span>)
event5Js: play.api.libs.json.JsValue = {<span style="color: #ffa07a;">"kind"</span>:<span style="color: #ffa07a;">"t1"</span>,<span style="color: #ffa07a;">"payloads"</span>:{<span style="color: #ffa07a;">"userId"</span><span style="color: #b0c4de;">:4</span>,<span style="color: #ffa07a;">"messages"</span>:[{<span style="color: #ffa07a;">"id"</span><span style="color: #b0c4de;">:5</span>},{<span style="color: #ffa07a;">"msg"</span>:<span style="color: #ffa07a;">"bye!"</span>}]}}
</pre>
Pay attention, that Json for QuitMessage and Heartbeat classes has no type information or anything! It's just plain json with domain fields only.
That's why messageReads has those jsHas occurances in its implementation, that is a little help for identifying which json object is what subtype of Message.
Lets see that reading from json still works:
<pre>
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">event6</span> = Json.<span style="color: #24BEE3;">fromJson</span>[Event[Messages]](<span style="color: #73ff73;">event5Js</span>)
even6: Event[Messages] = Event(t1,Messages(4,List(Heartbeat(5), QuitMessage(bye!))))
</pre>
Actually there is another way to do the same. You can inject an extra field into json object when serializing one of subtypes of an abstract type and use it
as a guidance for reconstructing objects from json. It is really easy. Lets modify messageReads and messageWrites like this:
<pre>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">messageWrites</span> = <span style="color: #24BEE3;">matchingWrites</span>[<span style="color: #084EA8;">Message</span>] {
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">m</span>: <span style="color: #98fb98;">QuitMessage => </span><span style="color: #24BEE3;">quitMessageJsFields</span><span style="color: #98fb98;">.</span><span style="color: #24BEE3;">extra</span>(<span style="color: #eedd82;">'</span><span style="color: #00ffff;">type</span> <span style="color: #24BEE3;">-></span> <span style="color: #eedd82;">'quit</span>)<span style="color: #eedd82;">.</span><span style="color: #24BEE3;">toWrites</span><span style="color: #eedd82;">.</span><span style="color: #24BEE3;">writes</span>(<span style="color: #eedd82;">m</span>)
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">m</span>: <span style="color: #98fb98;">Heartbeat => </span><span style="color: #24BEE3;">heartbeatJsFields</span><span style="color: #98fb98;">.</span><span style="color: #24BEE3;">extra</span>(<span style="color: #eedd82;">'</span><span style="color: #00ffff;">type</span> <span style="color: #24BEE3;">-></span> <span style="color: #eedd82;">'heart</span>)<span style="color: #eedd82;">.</span><span style="color: #24BEE3;">toWrites</span><span style="color: #eedd82;">.</span><span style="color: #24BEE3;">writes</span>(<span style="color: #eedd82;">m</span>)
}
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">messageReads</span>: <span style="color: #084EA8;">Reads[Message]</span> =
<span style="color: #24BEE3;">predicatedReads</span>[<span style="color: #084EA8;">Message</span>](
<span style="color: #24BEE3;">jsHas</span>('<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">-></span> 'quit) -> <span style="color: #24BEE3;">quitMessageJsFactory</span>,
<span style="color: #24BEE3;">jsHas</span>('<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">-></span> 'heart) -> <span style="color: #24BEE3;">heartbeatJsFactory</span>
)
</pre>
Now we test the new implementation.
<pre>
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">messages</span> = List(<span style="color: #026DF7;">Heartbeat</span>(1), <span style="color: #026DF7;">QuitMessage</span>(<span style="color: #ffa07a;">"Hello"</span>), <span style="color: #026DF7;">Heartbeat</span>(99))
messages: List[Product with Serializable with Message] = List(Heartbeat(1), QuitMessage(Hello), Heartbeat(99))
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">messagesJs</span> = <span style="color: #026DF7;">Json</span>.<span style="color: #24BEE3;">toJson</span>(<span style="color: #73ff73;">messages</span>)
messagesJs: play.api.libs.json.JsValue = [{<span style="color: #ffa07a;">"type"</span>:<span style="color: #ffa07a;">"heart"</span>,<span style="color: #ffa07a;">"id"</span><span style="color: #b0c4de;">:1</span>},{<span style="color: #ffa07a;">"type"</span>:<span style="color: #ffa07a;">"quit"</span>,<span style="color: #ffa07a;">"msg"</span>:<span style="color: #ffa07a;">"Hello"</span>},{<span style="color: #ffa07a;">"type"</span>:<span style="color: #ffa07a;">"heart"</span>,<span style="color: #ffa07a;">"id"</span><span style="color: #b0c4de;">:99</span>}]
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">messages2</span> = <span style="color: #026DF7;">Json</span>.<span style="color: #24BEE3;">fromJson</span>[<span style="color: #98fb98;">List[Message]</span>](<span style="color: #73ff73;">messagesJs</span>)
messages2: List[Message] = List(Heartbeat(1), QuitMessage(Hello), Heartbeat(99))
</pre>
That's not all. What if there is an information in a case class you don't want to reveal in JSON? Consider the following case class.
I will annotate fields that are safe to export by Ok annotation:
<pre>
@annotation.meta.getter
<span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Ok</span> <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">annotation.StaticAnnotation</span>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">User</span>(<span style="color: #13ff12;">@Ok</span> <span style="color: #eedd82;">login</span>: <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">@Ok</span> <span style="color: #eedd82;">fullName</span>: <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">@Ok</span> <span style="color: #eedd82;">messages</span>: <span style="color: #98fb98;">Int</span> = 0,
<span style="color: #eedd82;">passwordHash</span>: <span style="color: #98fb98;">Option[String]</span> = None)
</pre>
Now it's quite natural to define json fields like this:
<pre>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">userJsFields</span> = <span style="color: #24BEE3;">annotatedFields</span>[<span style="color: #98fb98;">User</span>, <span style="color: #98fb98;">Ok</span>]('jsonate)
</pre>
Let see it in action:
<pre>
scala>
| <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">user</span> = <span style="color: #026DF7;">User</span>(login = <span style="color: #ffa07a;">"akshaal"</span>,
| fullName = <span style="color: #ffa07a;">"Evgeny Chukreev"</span>,
| messages = 10,
| passwordHash = <span style="color: #026DF7;">Some</span>(<span style="color: #ffa07a;">"4d18758602c08243d7c08f8c9e4463b0"</span>))
user: User = User(akshaal,Evgeny Chukreev,10,Some(4d18758602c08243d7c08f8c9e4463b0))
scala> <span style="color: #00ffff;">val</span> <span style="color: #73ff73;">userJs</span> = <span style="color: #026DF7;">Json</span>.<span style="color: #24BEE3;">toJson</span>(<span style="color: #73ff73;">user</span>)
userJs: play.api.libs.json.JsValue = {<span style="color: #ffa07a;">"login"</span>:<span style="color: #ffa07a;">"akshaal"</span>,<span style="color: #ffa07a;">"fullName"</span>:<span style="color: #ffa07a;">"Evgeny Chukreev"</span>,<span style="color: #ffa07a;">"messages"</span><span style="color: #b0c4de;">:10</span>}
</pre>
It works.. But you can do more. Recall (if you looked at the implementation) that jsonate function was defined like this:
<pre>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">jsonate</span>[<span style="color: #98fb98;">T</span><span style="color: #084EA8;">: Writes</span><span style="color: #ffff70;">](t</span>: <span style="color: #026DF7;">T</span>, <span style="color: #ffff70;">args</span>: <span style="color: #98fb98;">Any</span>): <span style="color: #084EA8;">JsValue</span> = <span style="color: #026DF7;">Json</span>.<span style="color: #24BEE3;">toJson</span>(<span style="color: #ffff70;">t</span>)
</pre>
The function is used to make a json value out of field's value. It is applied to each field.
So you can define your our function that does post-processing (pre-processing?) and use it with a macro. The second argument (args) might be a parameter set given to annotation,
this gives you even more power for writing complex json serialization easily.<BR/><BR/>
And not only JSON. Using the same approach you can (de)serialize object from/to XML...
<h3>Pros of akmacros-json</h3>
<ul>
<li>Domain classes are separated from any notion of JSON</li>
<li>Full control over serialization/deserialization</li>
<li>Easy to use</li>
<li>Easy to extend or implement your own</li>
<li>No runtime reflections used</li>
</ul>
<h3>Cons of akmacros-json</h3>
<ul>
<li>Depends on Jerkson which is officially unavailable for Scala 2.10 (you need to build it from <a href="https://github.com/akshaal/jerkson">my fork</a>
(in order to build it you will need also <a href="https://github.com/akshaal/simplespec">this forked project</a>))
</li>
<li>Includes a copy of Play Json</li>
<li>Scala 2.10-M7 has many bugs related to implicits, value classes... so implementation as you might have noticed is not perfect in terms
of performance (defs are used instead of vals)
</li>
</ul>
I hope things will change really soon with the release of Scala 2.10.
<h3>About general purpose macros</h3>
This tiny macros addition is built on top of Play JSON (which is built on top of Jerkson (which is built on top of Jackson))
and akmacros (which doesn't have dependencies).
Checkout 78 lines long implementation <a href="https://github.com/akshaal/akmacros-json/blob/master/src/main/scala/info/akshaal/play.scala">here</a>.
See <a href="https://github.com/akshaal/akmacros">https://github.com/akshaal/akmacros</a> for a bit more
information about using akmacros with sbt.
Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6273818684831454306.post-46254767033143447892012-09-05T00:46:00.000+03:002012-09-10T21:59:19.427+03:00Easily implementing Json serialization for Play using macro. Function by symbol (lisp-like)lI liked the idea of using <a href="http://www.akshaal.info/2012/08/scala-210-annotated-fields-macro.html">fields macro</a> in scala so much so I created a dedicated project for this macro to start reusing it in the different projects I did. <a href="https://github.com/akshaal/akmacros">Here it is</a>.
<h3>Few words about implementation of this macro</h3>
The code has been reworked and now it is possible to transform field value using a supplied function. It means that Field and Fields types, and fields macro signatures are changed. In addition, there is a new convenient macro called allFields. I.e.:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Field</span>[<span style="color: #98fb98;">-I <: </span><span style="color: #026DF7;">AnyRef</span>, <span style="color: #98fb98;">+R</span>, <span style="color: #98fb98;">+A <: </span><span style="color: #026DF7;">Product</span>](<span style="color: #13ff12;">name</span>: <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">get</span>: <span style="color: #084EA8;">I => R</span>, <span style="color: #13ff12;">args</span>: <span style="color: #026DF7;">A</span>)
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">Fields</span>[-I <: <span style="color: #026DF7;">AnyRef</span>, +R, +A <: <span style="color: #026DF7;">Product</span>] = <span style="color: #98fb98;">List[Field[I, R, A]]</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">fields</span>[<span style="color: #98fb98;">Ann</span>, <span style="color: #98fb98;">I <: </span><span style="color: #026DF7;">AnyRef</span>, <span style="color: #98fb98;">R</span>, <span style="color: #98fb98;">Args <: </span><span style="color: #026DF7;">Product</span>](<span style="color: #ffff70;">apply</span>: <span style="color: #98fb98;">Symbol</span>) = macro <span style="color: #24BEE3;">fieldsImpl</span>[<span style="color: #026DF7;">Ann</span>, <span style="color: #026DF7;">I</span>, <span style="color: #026DF7;">R</span>, <span style="color: #026DF7;">Args</span>]
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">allFields</span>[<span style="color: #98fb98;">I <: </span><span style="color: #026DF7;">AnyRef</span>, <span style="color: #98fb98;">R</span>](<span style="color: #ffff70;">apply</span>: <span style="color: #98fb98;">Symbol</span>) = macro <span style="color: #24BEE3;">fieldsImpl</span>[<span style="color: #98fb98;">Any</span>, <span style="color: #026DF7;">I</span>, <span style="color: #026DF7;">R</span>, <span style="color: #98fb98;">None.type</span>]
</pre>
allFields is the new function that lists all public value members enclosed in the given type regardless of annotations.
R in a Field(s) type stands for RETURN and represents type of a value returned by the field getter transformed using the supplied function. Value transformer function is passed into the macro using its symbol. You might wonder why aren't we just using something like <span style="color: #eedd82;">f</span> : <span style="color: #98fb98;">T => R </span><span style="color: #00ffff;">forSome</span> { <span style="color: #00ffff;">type</span> <span style="color: #98fb98;">T</span> } ? That's because you can't pass the following function that way:
<pre>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">trans</span>[<span style="color: #98fb98;">T</span> : <span style="color: #98fb98;">TypeClass</span>](x : T) : Int = ???
</pre>
(which is just a sweet way of saving some keystrokes by not writing this:)
<pre>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">trans</span>[<span style="color: #98fb98;">T</span>](<span style="color: #eedd82;">x</span> : <span style="color: #98fb98;">T</span>)(<span style="color: #00ffff;">implicit</span> <span style="color: #eedd82;">tc</span> : <span style="color: #98fb98;">TypeClass[T]</span>) : <span style="color: #98fb98;">Int </span>= ???
</pre>
i.e. you can't pass function with TWO parameters (one of which is implicit parameter) where a ONE parameter function is expected. That is quite obvious but anyway.. So we use Symbol (in the spirit of Lisp). As you might guess by looking at the snippet below, it is expected that the symbol is constructed directly and not passed by reference:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">applyFunName</span> =
<span style="color: #ffff70;">apply</span>.<span style="color: #24BEE3;">tree</span> <span style="color: #00ffff;">match</span> {
<span style="color: #00ffff;">case</span> <span style="color: #98fb98;">Apply</span>(<span style="color: #eedd82;">_</span>, <span style="color: #98fb98;">List</span>(<span style="color: #98fb98;">Literal</span>(<span style="color: #98fb98;">Constant</span>(<span style="color: #eedd82;">s</span>)))) => <span style="color: #73ff73;">s</span>.<span style="color: #24BEE3;">toString</span>
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">_</span> =>
<span style="color: #ffff70;">c</span>.<span style="color: #24BEE3;">abort</span>(<span style="color: #ffff70;">apply</span>.<span style="color: #24BEE3;">tree</span>.<span style="color: #24BEE3;">pos</span>,
<span style="color: #ffa07a;">"fields macro is expected to be used with symbol literal like 'nothing or 'myFunction</span><span style="color: #ffa07a;">"</span>)
}
</pre>
Almost everything in the macro implementation remains more-less same, except part that constructs expression for getting field value out of object. Now it looks like this:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">applyFunTree</span> = c.parse(applyFunName)
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">getFunArgTree</span> = ValDef(Modifiers(), newTermName(<span style="color: #ffa07a;">"x"</span>), TypeTree(instanceT), EmptyTree)
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">getFunBodyTree</span> =
treeBuild.mkMethodCall(applyFunTree,
List(Select(Ident(newTermName(<span style="color: #ffa07a;">"x"</span>)), newTermName(name)),
argsTree))
</pre>
getFunBodyTree illustrates what signature is really expected for the transformer function: in addition to field value, all arguments of the annotation are passed into the function (or None if no annotation used or annotation has no arguments). For example, you can't use Predef.identity function, instead, you should use valueIdentity which is (already) defined like this:
<pre>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">valueIdentity</span>[<span style="color: #98fb98;">X</span>] (<span style="color: #ffff70;">value</span> : <span style="color: #026DF7;">X</span>, <span style="color: #ffff70;">annotationArgs</span> : <span style="color: #98fb98;">Any</span>) : <span style="color: #026DF7;">X</span><span style="color: #98fb98;"> </span>= <span style="color: #ffff70;">value</span>
</pre>
Having annotation arguments provided for the currently processing field gives you possibility for further customization of how the value is transformed. Now lets do an example.
<h3>Real-world example</h3>
Suppose you want to serialize your custom classes into JSON with no boilerplate code what so ever. That is how you can do it with this only (general-purpose) macro. Lets define some generic Writes typeclase provider:
<pre>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">writesForFields</span>[<span style="color: #98fb98;">T <: </span><span style="color: #026DF7;">AnyRef</span>](<span style="color: #00ffff;">implicit</span> <span style="color: #ffff70;">fields</span>: <span style="color: #98fb98;">clazz.Fields[T, JsValue, _]</span>): <span style="color: #084EA8;">Writes[T]</span> = {
<span style="color: #00ffff;">new</span> <span style="color: #084EA8;">Writes[T]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">writes</span>(<span style="color: #ffff70;">t</span>: <span style="color: #026DF7;">T</span>): <span style="color: #084EA8;">JsValue</span><span style="color: #98fb98;"> </span>= {
<span style="color: #026DF7;">JsObject</span>(<span style="color: #ffff70;">fields</span> <span style="color: #24BEE3;">map</span> {
(<span style="color: #ffff70;">field</span>: <span style="color: #98fb98;">clazz.Field[T, JsValue, _]</span>) =>
<span style="color: #ffff70;">field</span>.<span style="color: #13ff12;">name</span> -> <span style="color: #ffff70;">field</span>.<span style="color: #13ff12;">get</span>(<span style="color: #ffff70;">t</span>: <span style="color: #026DF7;">T</span>)
})
}
}
}
</pre>
The function shown above implicitly creates Writes for any type T which has an implicit instance of type Fields[T, JsValue, _] (read it like <i>"List of fields of class T along with function to get value of type JsValue for each field"</i>). Now lets define the transformer function, it will be used for serialization of field values:
<pre>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">asJsValue</span>[<span style="color: #98fb98;">T : Writes</span>](<span style="color: #eedd82;">v</span> : <span style="color: #98fb98;">T</span>, <span style="color: #eedd82;">annArgs</span> : <span style="color: #98fb98;">Any</span>) : <span style="color: #98fb98;">JsValue </span>= Json.toJson(v)
</pre>
That was the only code needed to bootstrap your mini-serialization framework. Now you can use it. Lets assume you have declarations:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">JquerySocketEvent</span>[<span style="color: #98fb98;">T</span>](<span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">data:</span> <span style="color: #026DF7;">T</span>, <span style="color: #13ff12;">`type</span><span style="color: #eedd82;">`:</span> <span style="color: #98fb98;">String</span> = <span style="color: #ffa07a;">"message"</span>, <span style="color: #13ff12;">reply:</span> <span style="color: #98fb98;">Boolean</span> = <span style="color: #7fffd4;">false</span>)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">ChatMessage</span>(<span style="color: #13ff12;">user</span>: <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">text</span>: <span style="color: #98fb98;">String</span>)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">jquerySocketEventJsFields</span>[<span style="color: #98fb98;">T</span><span style="color: #084EA8;">: Writes</span><span style="color: #ffff70;">]</span> = clazz.allFields[JquerySocketEvent[T], JsValue]('asJsValue)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">val</span> <span style="color: #87cefa;">chatMessageJsFields</span> = clazz.allFields[ChatMessage, JsValue]('asJsValue)
</pre>
That's it. ... some fun:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">event</span> = <span style="color: #026DF7;">JquerySocketEvent</span>(id = 1, data = <span style="color: #026DF7;">ChatMessage</span>(<span style="color: #ffa07a;">"Fluttershy"</span>, <span style="color: #ffa07a;">"</span><span style="color: #cdad00; font-weight: bold; text-decoration: underline;"><a href="http://t.co/wZqXBxKn">Yay</a></span><span style="color: #ffa07a;">!"</span>))
<span style="color: #24BEE3;">println</span> (<span style="color: #026DF7;">Json</span>.<span style="color: #24BEE3;">toJson</span>(<span style="color: #73ff73;">event</span>))
</pre>
... prints:
<pre>
{"id":1,"data":{"user":"Fluttershy","text":"<a href="http://t.co/wZqXBxKn">Yay</a>!"},"type":"message","reply":false}
</pre>
That was easy enough. Feel free to use it, re-implement it or implement a more powerful stuff. Macros FTW!
Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com7tag:blogger.com,1999:blog-6273818684831454306.post-553312670084831092012-08-25T17:45:00.001+03:002012-09-04T22:49:45.507+03:00Kiama & macroHere is a quick and dirty example of using <a href="http://code.google.com/p/kiama/">kiama</a> along with <a href="http://www.akshaal.info/2012/08/scala-210-annotated-fields-macro.html">the fields macro</a>.
<pre>
<span style="color: #00ffff;">import</span> annotated.{ Field => AnnField, Fields => AnnFields }
<span style="color: #00ffff;">object</span> <span style="color: #eedd82;">pp</span> {
<span style="color: #00ffff;">private</span> <span style="color: #00ffff;">object</span> <span style="color: #eedd82;">kpp</span> <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">org.kiama.output.PrettyPrinter</span>
<span style="color: #00ffff;">import</span> kpp._
<span style="color: #00ffff;">private</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">anyToDoc</span>(<span style="color: #ffff70;">any</span> : <span style="color: #98fb98;">Any</span>) : <span style="color: #98fb98;">Doc </span>=
<span style="color: #ffff70;">any</span> <span style="color: #00ffff;">match</span> {
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">song</span> : <span style="color: #026DF7;">Song</span><span style="color: #98fb98;"> => </span><span style="color: #24BEE3;">annotatedToDoc</span>(<span style="color: #ffa07a;">"Song"</span>, <span style="color: #73ff73;">song</span>, <span style="color: #73ff73;">songFields</span>)
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">artist</span> : <span style="color: #026DF7;">Artist</span><span style="color: #98fb98;"> => </span><span style="color: #24BEE3;">annotatedToDoc</span>(<span style="color: #ffa07a;">"Artist"</span>, <span style="color: #73ff73;">artist</span>, <span style="color: #73ff73;">artistFields</span>)
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">comp</span> : <span style="color: #026DF7;">Compilation</span><span style="color: #98fb98;"> => </span><span style="color: #24BEE3;">annotatedToDoc</span>(<span style="color: #ffa07a;">"Compilation"</span>, <span style="color: #73ff73;">comp</span>, <span style="color: #73ff73;">compilationFields</span>)
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">job</span> : <span style="color: #026DF7;">UploaderJob</span><span style="color: #98fb98;"> => </span><span style="color: #24BEE3;">annotatedToDoc</span>(<span style="color: #ffa07a;">"Job"</span>, <span style="color: #73ff73;">job</span>, <span style="color: #73ff73;">uploaderJobFields</span>)
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">map</span> : <span style="color: #084EA8;">Map[_, _]</span><span style="color: #98fb98;"> =></span>
<span style="color: #24BEE3;">list</span>(<span style="color: #73ff73;">map</span>.<span style="color: #24BEE3;">iterator</span>.<span style="color: #24BEE3;">toList</span>,
prefix = <span style="color: #ffa07a;">"Map"</span>,
elemToDoc = {
(<span style="color: #ffff70;">pair</span> : <span style="color: #98fb98;">(Any, Any)</span>) =>
<span style="color: #24BEE3;">anyToDoc</span>(<span style="color: #ffff70;">pair</span>.<span style="color: #13ff12;">_1</span>) <> <span style="color: #ffa07a;">" -> "</span> <> <span style="color: #24BEE3;">nest</span>(<span style="color: #24BEE3;">anyToDoc</span>(<span style="color: #ffff70;">pair</span>.<span style="color: #13ff12;">_2</span>))
})
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">seq</span> : <span style="color: #084EA8;">Seq[_]</span><span style="color: #98fb98;"> =></span>
<span style="color: #24BEE3;">list</span>(<span style="color: #73ff73;">seq</span>.<span style="color: #24BEE3;">toList</span>, prefix = <span style="color: #ffa07a;">"Sequence"</span>, elemToDoc = anyToDoc)
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">_</span> => <span style="color: #24BEE3;">value</span>(<span style="color: #ffff70;">any</span>)
}
<span style="color: #00ffff;">private</span> <span style="color: #00ffff;">def</span> <span style="color: #87cefa;">annotatedToDoc</span>[<span style="color: #98fb98;">T <: </span><span style="color: #026DF7;">AnyRef</span>](<span style="color: #ffff70;">name</span> : <span style="color: #98fb98;">String</span>, <span style="color: #ffff70;">t</span> : <span style="color: #026DF7;">T</span>, <span style="color: #ffff70;">fields</span> : <span style="color: #98fb98;">AnnFields[T, FieldArgs]</span>) : <span style="color: #98fb98;">Doc </span>= {
<span style="color: #24BEE3;">list</span>(<span style="color: #ffff70;">fields</span>,
prefix = <span style="color: #ffff70;">name</span>,
elemToDoc = {
(<span style="color: #ffff70;">f</span> : <span style="color: #98fb98;">AnnField[T, FieldArgs]</span>) =>
<span style="color: #ffff70;">f</span>.<span style="color: #13ff12;">name</span> <> <span style="color: #ffa07a;">" = "</span> <> <span style="color: #24BEE3;">anyToDoc</span>(<span style="color: #ffff70;">f</span>.<span style="color: #13ff12;">get</span>(<span style="color: #ffff70;">t</span>))
})
}
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">apply</span>(<span style="color: #ffff70;">any</span> : <span style="color: #98fb98;">Any</span>) : <span style="color: #98fb98;">String </span>= <span style="color: #24BEE3;">pretty</span>(<span style="color: #24BEE3;">anyToDoc</span>(<span style="color: #ffff70;">any</span>))
}
</pre>
pp (uploaderJob) easily prints data like this:
<pre>
Job(
artistMap = Map(
KeyRef(1x) -> Artist(
handle = Tester2,
marks = Set(),
id = None,
name = None,
website = None,
country = None,
location = None),
KeyRef(2u) -> Artist(
handle = Tester,
marks = Set(),
id = None,
name = None,
website = None,
country = None,
location = None)),
songMap = Map(
KeyRef(6t) -> Song(
relativePath = Path(test7.mp3),
title = test7,
artistRefs = Set(KeyRef(2u)),
tags = Set(),
id = None,
sourceId = None,
mixSongId = None,
year = Some(1999)),
KeyRef(5c) -> Song(
relativePath = Path(test6.mp3),
title = test6,
artistRefs = Set(KeyRef(2u)),
tags = Set(),
id = None,
sourceId = None,
mixSongId = None,
year = Some(1999)),
KeyRef(9i) -> Song(
relativePath = Path(test3.mp3),
title = test3,
artistRefs = Set(KeyRef(1x)),
tags = Set(),
id = None,
sourceId = Some(4),
mixSongId = None,
year = None),
KeyRef(3w) -> Song(
relativePath = Path(test5.mp3),
title = test5,
artistRefs = Set(KeyRef(2u)),
tags = Set(),
id = None,
sourceId = None,
mixSongId = None,
year = Some(1999)),
KeyRef(7l) -> Song(
relativePath = Path(test.mp3),
title = test,
artistRefs = Set(),
tags = Set(),
id = None,
sourceId = None,
mixSongId = None,
year = None),
KeyRef(8f) -> Song(
relativePath = Path(test2.mp3),
title = test2,
artistRefs = Set(),
tags = Set(),
id = None,
sourceId = None,
mixSongId = None,
year = None),
KeyRef(4p) -> Song(
relativePath = Path(test4.mp3),
title = test4,
artistRefs = Set(KeyRef(2u)),
tags = Set(),
id = None,
sourceId = None,
mixSongId = None,
year = Some(1999)),
compilationMap = Map(
KeyRef(11x) -> Compilation(
title = Compo,
songRefs = Sequence(KeyRef(6t)),
marks = Set(),
year = Some(1999),
cdOrSide = Some(B)),
KeyRef(10g) -> Compilation(
title = Compo,
songRefs = Sequence(KeyRef(3w), KeyRef(4p), KeyRef(5c)),
marks = Set(),
year = Some(1999),
cdOrSide = Some(A))))
</pre>
<ad></ad>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6273818684831454306.post-90844004484110500612012-08-18T21:14:00.001+03:002012-09-04T22:47:18.928+03:00Scala 2.10: annotated fields macroHere is a short example of how one can leverage <a href="http://docs.scala-lang.org/sips/pending/self-cleaning-macros.html">SIP-16</a> introduced in Scala-2.10.<br>
(The source code you will find below is expected to be compiled on Scala 2.10-M7. Note, that -M6 provides a slightly different set of API for macro.)
<br><br>
Lets define a macro that makes it possible to traverse value fields of a (case) class. First, we import what we will use:
<pre>
<span style="color: #00ffff;">import</span> language.experimental.macros
<span style="color: #00ffff;">import</span> scala.reflect.macros.Context
<span style="color: #00ffff;">import</span> scala.annotation.Annotation
</pre>
The macros, we are implementing, will be located in 'annotated' object since Scala allows usage of type aliases inside object (unlike package namespace).
<pre>
<span style="color: #00ffff;">object</span> <span style="color: #eedd82;">annotated</span> {
</pre>
Any field belongs to a class (denoted as I). An annotated field might have useful information given by arguments on annotation. Type of the annotation arguments is denoted as A. Here is the definition of the Field class:
<pre>
<span style="color: #ff7f24;">/**</span><span style="color: #ff7f24;">
* An object of this class represents an annotated field.
* @tparam I type of class the field belongs to
* @tparam A type of annotation arguments (TupleX or None)
* @param name name of the field
* @param get function that returns field value of an instance given as argument to the function
* @param args list of arguments to the annotation found on the field
*/</span>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Field</span>[<span style="color: #98fb98;">I <: </span><span style="color: #026DF7;">AnyRef</span>, <span style="color: #98fb98;">A <: </span><span style="color: #026DF7;">Product</span>](<span style="color: #13ff12;">name</span> : <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">get</span> : <span style="color: #084EA8;">I => Any</span>, <span style="color: #13ff12;">args</span> : <span style="color: #026DF7;">A</span>)
</pre>
Here is the type alias to save some typing:
<pre>
<span style="color: #ff7f24;">/**</span><span style="color: #ff7f24;">
* List of fields belonging to the given type.
* @tparam I Owner of fields
* @tparam A type of annotation arguments (TupleX or None)
*/</span>
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">Fields</span>[I <: <span style="color: #026DF7;">AnyRef</span>, A <: <span style="color: #026DF7;">Product</span>] = <span style="color: #98fb98;">List[Field[I, A]]</span>
</pre>
That is how our macro is supposed to be seen by developers (i.e. it is supposed to be seen as an ordinary function):
<pre>
<span style="color: #ff7f24;">/**</span><span style="color: #ff7f24;">
* Macro which inspects class 'I' and returns a list of fields annotated with annotation 'Ann'.
* @tparam Ann search for field with this annotation
* @tparam Args type of arguments in the annotation (TupleX or None)
* @tparam I type of class to scan for annotated fields
*/</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">fields</span>[<span style="color: #98fb98;">Ann <: </span><span style="color: #026DF7;">Annotation</span>, <span style="color: #98fb98;">Args <: </span><span style="color: #026DF7;">Product</span>, <span style="color: #98fb98;">I <: </span><span style="color: #026DF7;">AnyRef</span>] = macro <span style="color: #24BEE3;">fieldsImpl</span>[<span style="color: #026DF7;">Ann</span>, <span style="color: #026DF7;">Args</span>, <span style="color: #026DF7;">I</span>]
</pre>
Finally, here is the implementation of the macro itself. The implementation is called by the scala compiler whenever it sees 'fields' macro:
<pre>
<span style="color: #ff7f24;">/**</span><span style="color: #ff7f24;">
* Implementation of the fields macro.
*/</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">fieldsImpl</span>[<span style="color: #98fb98;">AnnTT <: </span><span style="color: #026DF7;">Annotation</span><span style="color: #98fb98;"> </span><span style="color: #ffff70;">: c.AbsTypeTag</span>,
<span style="color: #98fb98;">Args <:</span> <span style="color: #026DF7;">Product</span> <span style="color: #ffff70;">: c.AbsTypeTag</span>,
<span style="color: #98fb98;">ITT <:</span> <span style="color: #026DF7;">AnyRef</span> <span style="color: #ffff70;">: c.AbsTypeTag</span>](<span style="color: #ffff70;">c</span> : <span style="color: #084EA8;">Context</span>) : <span style="color: #084EA8;">c.Expr[Fields[ITT, Args]]</span> = {
</pre>
("Args <: Product : c.AbsTypeTag" means "type Args is a subtype of type Product and there is an implicit value of type c.AbsTypeTag[Args]")<br>
Note that here and further below we use types (like AbsTypeTag) which are from the context 'c'. That is a compilation context of the application which the scala compiler will construct for the source code where the macro invocation is faced (not exactly but..).<br>
<br>
Now lets import types and values (like Select, Ident, newTermName) from the universe of the application the macro is currently used in:
<pre>
<span style="color: #00ffff;">import</span> c.universe._
</pre>
Lets materialize some types as objects for further manipulation:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">instanceT</span> = <span style="color: #24BEE3;">implicitly</span>[<span style="color: #026DF7;">c.AbsTypeTag</span>[ITT]].<span style="color: #24BEE3;">tpe</span>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">annT</span> = <span style="color: #24BEE3;">implicitly</span>[<span style="color: #026DF7;">c.AbsTypeTag</span>[AnnTT]].<span style="color: #24BEE3;">tpe</span>
</pre>
Now, some real action: get annotated fields. Note that hasAnnotation doesn't work for a reason I don't know...
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">annSymbol</span> = <span style="color: #73ff73;">annT.typeSymbol</span>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">fields</span> = <span style="color: #73ff73;">instanceT</span>.<span style="color: #24BEE3;">members</span> <span style="color: #24BEE3;">filter</span> (<span style="color: #ffff70;">member</span> => <span style="color: #ffff70;">member</span>.<span style="color: #24BEE3;">getAnnotations</span>.<span style="color: #24BEE3;">exists</span>(<span style="color: #ffff70;">_</span>.<span style="color: #24BEE3;">atp</span> == <span style="color: #73ff73;">annT</span>))
</pre>
It is convenient to have a helper function. This function will fold given expression sequence into a new expression that creates List of expressions at runtime ;-)
<pre>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">foldIntoListExpr</span>[<span style="color: #98fb98;">T</span> <span style="color: #ffff70;">: c.AbsTypeTag</span>](<span style="color: #ffff70;">exprs</span> : <span style="color: #084EA8;">Iterable[c.Expr[T]]</span>) : <span style="color: #084EA8;">c.Expr[List[T]]</span> =
<span style="color: #ffff70;">exprs</span>.<span style="color: #24BEE3;">foldLeft</span>(reify { Nil : List[T] }) {
(<span style="color: #ffff70;">accumExpr</span>, <span style="color: #ffff70;">expr</span>) =>
reify { expr.splice :: accumExpr.splice }
}
</pre>
For each field, construct expression that will instantiate Field object at runtime:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">fieldExprs</span> =
<span style="color: #00ffff;">for</span> (field <- <span style="color: #73ff73;">fields</span>)<span style="color: #24BEE3;"> yield</span> {
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">argTrees</span> = field.getAnnotations.find(_.atp == annT).get.args
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">name</span> = field.name.toString.trim <span style="color: #ff7f24;">// </span><span style="color: #ff7f24;">Why is there a space at the end of field name?!
</span> <span style="color: #00ffff;">val</span> <span style="color: #eedd82;">nameExpr</span> = c literal name
<span style="color: #ff7f24;">// </span><span style="color: #ff7f24;">Construct arguments list expression
</span> <span style="color: #00ffff;">val</span> <span style="color: #eedd82;">argsExpr</span> =
<span style="color: #00ffff;">if</span> (argTrees.isEmpty) {
c.Expr [Args] (Select(Ident(newTermName(<span style="color: #ffa07a;">"scala"</span>)), newTermName(<span style="color: #ffa07a;">"None"</span>)))
} <span style="color: #00ffff;">else</span> {
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">tupleConstTree</span> = Select(Select(Ident(newTermName (<span style="color: #ffa07a;">"scala"</span>)),
newTermName(s<span style="color: #ffa07a;">"Tuple${argTrees.size}"</span>)),
newTermName(<span style="color: #ffa07a;">"apply"</span>))
c.Expr [Args] (Apply (tupleConstTree, argTrees))
}
<span style="color: #ff7f24;">// </span><span style="color: #ff7f24;">Construct expression (x : $I) => x.$name
</span> <span style="color: #00ffff;">val</span> <span style="color: #eedd82;">getFunArgTree</span> = ValDef(Modifiers(), newTermName(<span style="color: #ffa07a;">"x"</span>), TypeTree(instanceT), EmptyTree)
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">getFunBodyTree</span> = Select(Ident(newTermName(<span style="color: #ffa07a;">"x"</span>)), newTermName(name))
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">getFunExpr</span> = c.Expr[ITT => Any](Function(List(getFunArgTree), getFunBodyTree))
reify {
Field[ITT, Args](name = nameExpr.splice, get = getFunExpr.splice, args = argsExpr.splice)
}
}
</pre>
By this moment, value fieldExprs will contain something like
<pre>
List (
reify {Field ('field1', (x => x.field1), (..)},
reify {Field ('field2', (x => x.field2), (..)}
)
</pre>
(where (..) are arguments of annotaiton on that field)<br>
Now we have to lift List construction into expression and we're done!
<pre>
<span style="color: #ff7f24;">// </span><span style="color: #ff7f24;">Construct expression list like field1 :: field2 :: Field3 ... :: Nil
</span> <span style="color: #24BEE3;">foldIntoListExpr</span>(<span style="color: #73ff73;">fieldExprs</span>)
}
}
</pre>
<br>
And finally lets have some fun. Lets test it in REPL! (beware that scala macros are supposed to be compiled before they are used)<br>
<pre>
scala> <span style="color: #00ffff;">type</span> <span style="color: #98fb98;">FormatFun</span> = <span style="color: #084EA8;">Any => Any</span>
defined type alias FormatFun
scala> <span style="color: #00ffff;">type</span> <span style="color: #98fb98;">PrettyArgs</span> = <span style="color: #98fb98;">(Option[String], FormatFun)</span>
defined type alias PrettyArgs
scala> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Pretty</span>(<span style="color: #13ff12;">aka</span> : <span style="color: #98fb98;">Option[String]</span> = None, <span style="color: #13ff12;">format</span> : <span style="color: #084EA8;">FormatFun</span> = identity) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">annotation.StaticAnnotation</span>
defined class Pretty
</pre>
<pre>
scala> :paste
// Entering paste mode (ctrl-D to finish)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">pp</span>[<span style="color: #98fb98;">X <: </span><span style="color: #026DF7;">AnyRef</span>](<span style="color: #ffff70;">fields</span> : <span style="color: #026DF7; ">annotated</span><span style="color: #026DF7;">.Fields</span><span style="color: #98fb98;">[X, PrettyArgs]</span>)(<span style="color: #ffff70;">x</span> : <span style="color: #026DF7;">X</span>) = {
<span style="color: #ffff70;">fields</span> <span style="color: #24BEE3;">map</span> {
<span style="color: #00ffff;">case</span> <span style="color: #eedd82;">annotated.</span><span style="color: #24BEE3;">Field</span>(<span style="color: #eedd82;">fieldName</span>, <span style="color: #eedd82;">get</span>, (<span style="color: #eedd82;">akaOpt</span>, <span style="color: #eedd82;">fmtFun</span>)) =>
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">name</span> = fieldName.<span style="color: #24BEE3;">replaceAll</span>(<span style="color: #ffa07a;">"([A-Z][a-z]+)"</span>, <span style="color: #ffa07a;">" $1"</span>).<span style="color: #24BEE3;">toLowerCase</span>.<span style="color: #24BEE3;">capitalize</span>
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">aka</span> = akaOpt <span style="color: #24BEE3;">map</span> (<span style="color: #ffa07a;">" (aka "</span> <span style="color: #24BEE3;">+</span> _ <span style="color: #24BEE3;">+</span> <span style="color: #ffa07a;">")"</span>) <span style="color: #24BEE3;">getOrElse</span> <span style="color: #ffa07a;">""</span>
<span style="color: #00ffff;">val</span> <span style="color: #eedd82;">value</span> = fmtFun(get(x))
s<span style="color: #ffa07a;">"$name$aka: $value"</span>
} <span style="color: #24BEE3;">mkString</span> <span style="color: #ffa07a;">"\n"</span>
}
// Exiting paste mode, now interpreting.
pp: [X <: AnyRef](fields: info.akshaal.radio.uploader.annotated.Fields[X,(Option[String], Any => Any)])(x: X)String
</pre>
<br>
Still no macro was used. Now here it comes. First, we define case class. Next, we gather annotated fields in the definition of personPrettyFields. When you run it in REPL, it is quite important to use :paste, otherwise annotation of the case class will be lost because subtypes of StaticAnnotation are visible during compilation only (REPL calls a new scala compiler for each expression reusing binary classes compiled during previous steps). So:
<pre>
scala> :paste
// Entering paste mode (ctrl-D to finish)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Person</span>(
<span style="color: #13ff12;">id</span> : <span style="color: #98fb98;">Int</span>,
<span style="color: #13ff12;">@Pretty</span>(aka = Some(<span style="color: #ffa07a;">"nickname"</span>)) name : <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">@Pretty</span> firstName : <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">@Pretty</span>(None, format = _.toString.toUpperCase) secondName : <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">@Pretty</span>(None, format = { <span style="color: #00ffff;">case</span> <span style="color: #eedd82;">x</span> : <span style="color: #98fb98;">Option[_] => x </span><span style="color: #eedd82;">getOrElse</span> <span style="color: #ffa07a;">""</span> <span style="color: #eedd82;">}</span>) twitter : <span style="color: #98fb98;">Option[String]</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">personPrettyFields</span> = <span style="color: #73ff73;">annotated</span>.<span style="color: #24BEE3;">fields</span>[<span style="color: #98fb98;">Pretty</span>, <span style="color: #98fb98;">PrettyArgs</span>, <span style="color: #98fb98;">Person</span>]
// Exiting paste mode, now interpreting.
defined class Person
personPrettyFields: List[info.akshaal.radio.uploader.annotated.Field[Person,(Option[String], Any => Any)]] =
List(Field(name,<function1>,(Some(nickname),<function1>)),
Field(firstName,<function1>,(None,<function1>)),
Field(secondName,<function1>,(None,<function1>)),
Field(twitter,<function1>,(None,<function1>)))
</pre>
(I've aligned the output of REPL a bit..)<br>
Lets check field names:
<pre>
scala> personPrettyFields <span style="color: #24BEE3;">map</span> (field => field.<span style="color: #24BEE3;">name</span>)
res0: List[String] = List(name, firstName, secondName, twitter)
</pre>
Here are getters of each field:
<pre>
scala> personPrettyFields <span style="color: #24BEE3;">map</span> (_.<span style="color: #24BEE3;">get</span>)
res1: List[Person => Any] = List(<function1>, <function1>, <function1>, <function1>)
</pre>
Now, lets create a Person object:
<pre>
scala> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">person1</span> = <span style="color: #026DF7;">Person</span>(1, <span style="color: #ffa07a;">"akshaal"</span>, <span style="color: #ffa07a;">"Evgeny"</span>, <span style="color: #ffa07a;">"Chukreev"</span>, <span style="color: #026DF7;">Some</span>(<span style="color: #ffa07a;">"https://twitter.com/Akshaal"</span>))
person1: Person = Person(1,akshaal,Evgeny,Chukreev,Some(https://twitter.com/Akshaal))
</pre>
... and a value for each field of this person:
<pre>
scala> personPrettyFields <span style="color: #24BEE3;">map</span> (_.<span style="color: #24BEE3;">get</span> (person1))
res2: List[Any] = List(akshaal, Evgeny, Chukreev, Some(https://twitter.com/Akshaal))
</pre>
Some more objects for more fun:
<pre>
scala> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">person2</span> = <span style="color: #026DF7;">Person</span>(2, <span style="color: #ffa07a;">"</span><span style="color: #ffa07a;">BillGates</span><span style="color: #ffa07a;">"</span>, <span style="color: #ffa07a;">"Bill"</span>, <span style="color: #ffa07a;">"Gates"</span>, <span style="color: #026DF7;">Some</span>(<span style="color: #ffa07a;">"https://twitter.com/</span><span style="color: #ffa07a;">BillGates</span><span style="color: #ffa07a;">"</span>))
person2: Person = Person(2,BillGates,Bill,Gates,Some(https://twitter.com/BillGates))
scala> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">persons</span> = List(<span style="color: #13ff12;">person1</span>, <span style="color: #13ff12;">person2</span>)
persons: List[Person] =
List(Person(1,akshaal,Evgeny,Chukreev,Some(https://twitter.com/Akshaal)),
Person(2,BillGates,Bill,Gates,Some(https://twitter.com/BillGates)))
scala> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">ppPerson</span> = <span style="color: #24BEE3;">pp</span>(personPrettyFields) _
ppPerson: Person => String = <function1>
</pre>
And finally:
<pre>
scala> persons <span style="color: #24BEE3;">map</span> ppPerson <span style="color: #24BEE3;">mkString</span> <span style="color: #ffa07a;">"\n----------------------------\n"</span>
res5: String =
Name (aka nickname): akshaal
First name: Evgeny
Second name: CHUKREEV
Twitter: https://twitter.com/Akshaal
----------------------------
Name (aka nickname): BillGates
First name: Bill
Second name: GATES
Twitter: https://twitter.com/BillGates
</pre>
<br>
That's all ;-)
You will find complete source code along with specs2 specification (with one more example) on the gist: <a href="https://gist.github.com/3388753">https://gist.github.com/3388753</a>
<br>
<h1>No animals were killed.</h1>
<h1>No types were casted.</h1>
<h1>No reflections were used.</h1>
<ad></ad>
Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com1