Inroduction to Macros in Scala.
Macros in Scala is an experimental feature introduced in scala.2.10.0. Scala runtime enables us to utilize the scala compiler’s Abstract Syntax Tree API to implement scala compiler-time reflection. This in return, allows our programs to modify them selves or generate new code during compilation time.
One advantage that macros possess is that they are based on the same API which is also used for compiler reflection and is provided in ‘scala.reflect.api’ package. Hence macros also has the ability to visit and manipulate generic code which is a feature of run time reflection. Macros in Scala is nothing but which simplifies the methods run by the type checker which can be quite complex and cause quite of headaches. complie-time programming has been set as most important and valuable tool for enabling programming techniques such as:- Language virtualization, program reification, self optimaztion and also algorithmic program construction.
This is to be simply that, macros(The reflection cousin) can be decomposed into two parts:
a). Introspection: a program can examine itself.
b). Intercession: a program can modify its state/meaning.
These are the two parts of macros reflection.
A brief example:
This portion of example provides an end-to-end implementation of a printf macro, which gives the validate and applies the format string at complie-scala runtime. Instead of simplicity the dicussion uses console compiler, As explained in the below given content macros are also supported by maven and sbt. In the beginning a macros starts with a macros definition, which presents the facode of the macro. Macro definition is a simple function with anything one might in its signature. it is nothing more but a reference to an implementation. As given above to define a macro one has to import scala.language.experiment. Macros are to start a special scala complier switch: language,experiment,macros.
import scala.reflect.macros.Context
def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = …
Compiler API is exposed in scala.reflect.macros.Context
. The most important part, reflection API, is accessible via. It’s customary to import c.universe._
, because it includes a lot of routinely used functions and types.
import c.universe._
Typical macros and this macros is not an excreation it needs to create ASTs [abstract syntax trees] which presents scala code. Across with creation of ASTs code given below also operates types of note how we get a grasp of scala types that correlate to INT and STRING. Reflection review linked above covers type of manipulation in brief. The last step of code generation merge all the generated code into a block. This illustration which provides a shortcut for creating ASTs.
val evals = ListBuffer[ValDef]()
def precompute(value: Tree, tpe: Type): Ident = {
val freshName = TermName(c.fresh("eval$"))
evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value)
Ident(freshName)
}
val paramsStack = Stack[Tree]((params map (_.tree)): _*)
val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map {
case "%d" => precompute(paramsStack.pop, typeOf[Int])
case "%s" => precompute(paramsStack.pop, typeOf[String])
case "%%" => Literal(Constant("%"))
case part => Literal(Constant(part))
}
val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree)
c.Expr[Unit](Block(stats.toList, Literal(Constant(()))))
The snippet below represents a complete definition of the printf
macro. To follow the example, create an empty directory and copy the code to a new file named Macros.scala
.
import scala.reflect.macros.Context import scala.collection.mutable.{ListBuffer, Stack} object Macros { def printf(format: String, params: Any*): Unit = macro printf_impl def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = { import c.universe._ val Literal(Constant(s_format: String)) = format.tree val evals = ListBuffer[ValDef]() def precompute(value: Tree, tpe: Type): Ident = { val freshName = TermName(c.fresh("eval$")) evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) Ident(freshName) } val paramsStack = Stack[Tree]((params map (_.tree)): _*) val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { case "%d" => precompute(paramsStack.pop, typeOf[Int]) case "%s" => precompute(paramsStack.pop, typeOf[String]) case "%%" => Literal(Constant("%")) case part => Literal(Constant(part)) } val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) } }
Reference to the above content is available here https://docs.scala-lang.org/overviews/macros/overview.html#a-complete-example