scala的宏定义

来源:互联网 发布:mac删除照片 编辑:程序博客网 时间:2024/05/19 18:48

其实宏的使用并不难,api已经帮我们做好了一切,我们只要关注如何使用获取宏参数和宏的返回值

一个例子:

import scala.reflect.macros.Contextimport scala.collection.mutable.{ListBuffer, Stack}object QStringImpl { def Qmap(p: Char => Int,elem:String):Int = macro QStringImpl.map_Impl def map_Impl(c: Context)(p: c.Expr[Char => Int],elem: c.Expr[String]): c.Expr[Int] = {        import c.universe._ //splice只能在reify使用,具体化,功能与reify相反        reify { elem.splice.map(p.splice).sum}        }}


 

import scala.language.experimental.macrosobject DemoTest2 extends App{import QStringImpl._val result = Qmap(_.toInt + 1,"111111")println(result)}


在scala中也有官方的实例,不过都是别人写的,个人觉得写得不好!正如我在上面说的,无论scala的宏是如何被官方实现的,对于程序员只需知道如何获取参数和返回参数,这是官方doc的教程

http://docs.scala-lang.org/overviews/macros/overview.html

自已google一下,找到一个比较好例子,很容易的可以理解了,对于宏的使用要注意全局变量的依赖

链接http://www.warski.org/blog/2012/12/starting-with-scala-macros-a-short-tutorial/

顺便贴出来

Starting with Scala Macros: a short tutorial

Using some time during the weekend, I decided to finally explore one the new features in the coming Scala 2.10, macros. Macros are also written in Scala so in essence a macro is a piece of Scala code, executed at compile-time, which manipulates and modifies the AST of a Scala program.

To do something useful, I wanted to implement a simple macro for debugging; I suppose I’m not alone in using println-debugging, that is debugging by inserting statements like:

1
println("After register; user = " + user + ", userCount = " + userCount)

running a test, and checking what the output is. Writing the variable name before the variable is tedious, so I wanted to write a macro which would do that for me; that is:

1
debug("After register", user, userCount)

should have the same effect as the first snippet (it should generate code similar to the one above).

Let’s see step-by-step how to implement such a macro. There’s a good getting started guide on the scala macros page, which I used. All code explained below is available on GitHub, in the scala-macro-debug project.

1. PROJECT SETUP

To experiment comfortably we’ll need to setup a simple project first. We will need at least two subprojects: one for macros, and one for testing the macros. That is because the macros must be compiled separately and before and code that uses them (as they influence the compilation process).

Moreover, the macro subproject needs to have a dependency on scala-compiler, to be able to access the reflection and AST classes.

A simple SBT build file could look like this: Build.scala.

2. HELLO WORLD!

“Hello World!” is always a great starting point. So my first step was to write a macro, which would expand hello() to println("Hello World!") at compile-time.

In the macros subproject, we have to create a new object, which defines hello() and the macro:

12345678910111213
package com.softwaremill.debug import language.experimental.macros import reflect.macros.Context object DebugMacros {  def hello(): Unit = macro hello_impl   def hello_impl(c: Context)(): c.Expr[Unit] = {    // TODO  }}

There are a couple of important things here:

  1. we have to import language.experimental.macros, to enable the macros feature in the given source file. Otherwise we’ll get compilation errors reminding us about the import.
  2. the definition of hello() uses the macro keyword, followed by a method which implements the macro
  3. the macro implementation has two parameter lists: the first is the context (you can think about it as a compilation context), the second mirrors the parameter list of our method – here it’s empty. Finally, the return type must also match – however in the method we have a return type unit, in the macro we return an expression (which wraps a piece of an AST) of type unit.

Now to the implementation, which is pretty short:

1234
def hello_impl(c: Context)(): c.Expr[Unit] = {  import c.universe._  reify { println("Hello World!") }}

Going line by line:

  1. first we import the “universe”, which gives convenient access to AST classes. Note that the return type is c.Expr – so it’s a path-dependent type, taken from the context. You’ll see that import in every macro.
  2. as we want to generate code which prints “Hello World!”, we need to create an AST for it. Instead of constructing it manually (which is possible, but doesn’t look too nice), Scala provides a reifymethod (reify is also a macro – a macro used when compiling macros :) ), which turns the given code into an Expr[T] (expressions wrap an AST and its type). As println has type unit, the reified expression has type Expr[Unit], and we can just return it.

Usage is pretty simple. In the testing subproject, write the following:

1234
object DebugExample extends App {  import DebugMacros._  hello()}

and run the code (e.g. with the run command in SBT shell).

3. PRINTING OUT A PARAMETER

Printing Hello World is nice, but it’s even nicer to print a parameter. The second macro will do just that: it will transform printparam(anything) into println(anything). Not very useful, and pretty similar to what we’ve seen, with two crucial differences:

123456
def printparam(param: Any): Unit = macro printparam_impl def printparam_impl(c: Context)(param: c.Expr[Any]): c.Expr[Unit] = {  import c.universe._  reify { println(param.splice) }}

The first difference is that the method accepts a parameter param: Any. In the macro implementation, we have to mirror that – but same as with the return type, instead of Any, we accept an Expr[Any], as during compile-time we operate on ASTs.

The second difference is the usage of splice. It is a special method of Expr, which can only be used inside a reify call, and does kind of the opposite of reify: it embeds the given expression into the code that is being reified. Here, we have param which is an Expr (that is, tree + type), and we want to put that tree as a child of println; we want the value that is represented by param to be passed toprintln, not the AST. splice called on an Expr[T] returns a T, so the reified code type-checks.

4. SINGLE-VARIABLE DEBUG

Let’s now get to our debug method. First maybe let’s implement a single-variable debug, that isdebug(x) should be transformed into something like println("x = " + x).

Here’s the macro:

123456789
def debug(param: Any): Unit = macro debug_impl def debug_impl(c: Context)(param: c.Expr[Any]): c.Expr[Unit] = {  import c.universe._  val paramRep = show(param.tree)  val paramRepTree = Literal(Constant(paramRep))  val paramRepExpr = c.Expr[String](paramRepTree)  reify { println(paramRepExpr.splice + " = " + param.splice) }}

The new thing is of course generating the prefix. To do that, we first turn the parameter’s tree into aString. The built-in method show does exactly that. A little note here; as we are turning an AST into a String, the output may look a bit different than in the original code. For vals declared inside a method, it will return simply the val name. For class fields, you’ll see something likeDebugExample.this.myField. For expressions, e.g. left + right, you’ll see left.+(right). Not perfect, but readable enough I think.

Secondly, we need to create a tree (by hand this time) representing a constant String. Here you just have to know what to construct, e.g. by inspecting trees created by reification (or reading Scala compiler’s source code ;) ).

Finally, we turn that simple tree into an expression of type String, and splice it inside the println. Running for example such code:

12345678910111213
object DebugExample extends App {  import DebugMacros._   val y = 10   def test() {    val p = 11    debug1(p)    debug1(p + y)  }   test()}

outputs:

12
p = 11p.+(DebugExample.this.y) = 21

5. FINAL PRODUCT

Implementing the full debug macro, as described above, introduces only one new concept. The full source is a bit long, so you can view it on GitHub.

In the macro implementation we first generate a tree (AST) for each parameter – which represents either printing a constant, or an expression. Then we interleave the trees with separators (", ") for easier reading.

Finally, we have to turn the list of trees into an expression. To do that, we create a Block. A block takes a list of statements that should be executed, and an expression which is a result of the whole block. In our case the result is of course ().

And now we can happily debug! For example, writing:

1
debug("After register", user, userCount)

will print, when executed:

1
AfterRegister, user = User(x, y), userCount = 1029

SUMMING UP

That’s quite a long post, glad somebody made it that far :). Anyway, macros look really interesting, and it’s pretty simple to start writing macros on your own. You can find a simple SBT project plus the code discussed here on GitHub (scala-macro-debug project). And I suppose soon we’ll see an outcrop of macro-leveraging projects. Already there are some, for example Expecty or Macrocosm.

EDIT 9/3/2013: the macro is now available in Maven central, see the README.

 

0 0
原创粉丝点击