Scala Macros -An overview


OVERVIEW

In this blog we will understand the basics of Macros and reflection. The main motive of the blog is to lay a foundation for better understanding of the the ‘Async library’.

The ‘MAGIC’ of macros ->

Macros can be called ‘special functions’ that are called by the compiler during compilation. These functions are special as using macros we can access the compiler API’s which provide us with the privileged to manage the AST(Abstract Syntax Tree) generated by the compiler. AST is a data structure used by the Scala compiler to store the information about the compiled code. Macros give programmer the power to :-

a). Inspecting type of an object, including generic type (Traversing, inspecting the AST)
b). Creating new objects.(Appending the AST with new child)
c). Access the member function of the object(Accessing the child nodes in AST)

In Scala macros are a way to implement compile time reflection. One advantage that macros possess is that they are based on the same API which is also used for Scala runtime 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.

Stated simply, 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.

Now let us programmatically understand how micro are implemented. For better Understanding I have split the example into following steps.

Step 1 ‘Declaring a macro definition’ -> (Fabricating the body)

To use macro in the project add following dependency to your ‘build.sbt’ :-

libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.11.7"

Macro definition follows the following prototype :

 

def m(x: T): R = macro implRef

Here,
‘m’ is the name of the method
‘x’ is the parameter of ‘m’
‘R’ is the return type of ‘m’
‘macro’ is the keyword
‘implref’ is another method which provide the implementation for macro

 

Macro definition for our example :-

def add(num1:Int, num2:Int):Int = macro addImpl

The above method takes two integers and returns its sum, the implementation of the addition operation is provided in addImpl method. Here ‘add’ is our macro whose implementation is provided in next step.

 

Step 2 ‘Implimenting a macro’ -> (Putting in the soal)

Macro implementation is the method which defines the functionality of macro. Though the macro implementation is a bit different from the normal method in a way that the macro implementation work on AST (Abstract Syntax Tree) and is called at the compile time with AST of the parameters rather than the parameter itself and also returns an AST of its return value.
Let us continue with the above example and provide functionality for the ‘add’ macro.

import language.experimental.macros
import reflect.macros.blackbox.Context

def add(num1:Int, num2:Int):Int = macro addImpl

def add_impl(c: Context)(num1: c.Expr[Int], num2: c.Expr[Int]): c.Expr[Int] = {
 import c.universe.reify
 reify {
 num1.splice + num2.splice
 }
 }

Here,
c is a context parameter that contains information collected by the compiler at compile time
‘num1’, ‘num2’ are the AST of the two operands to be added
‘addImpl’ has an AST of type integer as its return type.
The type ‘c.Expr[Int]’ means the parameters are AST’s of the expression with their type being integer.

Macros implementation method returns an AST and this is achieved using the ‘reify’ and ‘splice’ method.

a). reify() – The ‘reify’ method is in itself a macro which turns the code enclosed in its scope into its corresponding AST and type. In our example, ‘reify’ returns the AST of sum of the values of ‘num1’ and ‘num2′(values are obtained using splice) with the type of AST being integer.

b). splice() – The ‘splice’ method is a programmatic antonym of ‘reify’ as it turns the AST into a value with corresponding type. It can only be used inside the scope of ‘reify’. In our example, using ‘splice’ we convert ‘num1’ parameter which is an AST of type integer into a value of type integer.

Step 3 ‘The compiler magic’ -> (sparking in life)

In this step we will learn the sequence of events that occur when the compiler encounters a macro

Firstly, while compiling the code if compiler detects a macro, the corresponding macro implementation is evoked and AST of the parameters of the macro are send as the argument to macro implementation(using reflection).
Call to macro implementation transforms in following way :-

def add(num1:Int, num2:Int):Int = macro addImpl

is converted to


addImpl(c)(AST < 2 >, AST < 1 >) //here 2 and 1 are the parameters to macro(operands to be added)

Here AST< Expr > represents the abstract syntax tree for the expression ‘Expr’, this notation is used only for the blog actually the AST are made by ‘scala.reflect.api.Trees’. In reality the AST of the parameter ‘2’ would be as follows :

Literal(Constant(2))

Secondly, the return value of macro implementation which in itself is an AST gets inlined (attached) to the AST of the main program and is type checked in turn. In other words, the macro declaration acts as the part of main AST to which the AST of the returned value of macro implementation is attached.

A very important note is that the call to macro can not be present in same class in which the macro and its implementation are present. In this example the call to the macro is made in the test case(using scalatest) i.e. :

"MacroDemo" should "have tests" in {
 val expectedResult = MacroDemo.add(1,2)
 val actualResult = 3
 expectedResult should === (actualResult)
 }

With this an overview to macros and reflection comes to an end. We will now be focussing on ‘async’, ‘await’ of the Scala Async library.

 

Macro Implementation Git Code : https://github.com/knoldus/macros-example

References ->

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

2). http://docs.scala-lang.org/overviews/reflection/overview.html

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

Happy Coding …..

This entry was posted in Scala and tagged , , , , , , , . Bookmark the permalink.

4 Responses to Scala Macros -An overview

  1. Pingback: The Async library in scala | Knoldus

  2. Pingback: Getting Asynchronous in Scala : Part 2 (macro, reflection and Async library) | Knoldus

  3. jmoore315 says:

    Code/examples don’t seem to be rendering correctly (they appear then are removed when page is loaded). Tried without adblock & multiple browsers

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s