Because of the delay, partial solutions are more welcome than usual. Please try to solve at least two-three exercises, and focus on the theory part.
- Consider a macro named
aMacro
running on an expression with nested subexpressions:
val intermediateExpr = if (cond) { list map f } else { list map g }
aMacro(intermediateExpr map h)
We said that aMacro can't know statically the what intermediateExpr is going to contain. Sketch a scenario which exemplifies the problem.
-
Assume again that
aMacro
is a macro and consider the snippet below, divided in two fragments marked (1) and (2). WillaMacro
receive different arguments in examples (1) and (2)?//(1) aMacro(1) //(2) aMacro({ val v = 1 v })
If there is a difference between the arguments in those scenarios:
- can you describe scenarios where the difference is or is not interesting, and motivate why?
- For scenarios where the difference is not interesting, does the difference introduce potential for bugs?
-
Choose an example of boilerplate in the course lecture notes and try describing how macros could be used to automate the generation of such boilerplate.
-
Macros can also be used in the implementation of DSLs. For instance:
- for integrating external DSLs
- for implementing alternative semantics of existing code (language virtualization)
- to perform static analyses, even on code which does not get executed.
Give an example of a possible usage scenario among those categories, and/or point out additional categories with appropriate examples.
-
Inside
trace_impl
in the code from the lecture (14-macros/macros/01macros.scala
), we have two different proposals for the body:def trace(x: Any): Unit = macro trace_impl def trace_impl(c: Context)(x: c.Expr[Any]): c.Expr[Unit] = { import c.universe._ //Base version (1): //c.Expr(q"""println("The value of %s is %s" format (${show(x)}, $x))""") // //Question: what's wrong if we write instead (2): //c.Expr(q"""println(${"The value of %s is %s" format (show(x), x)})""") }
Please analyze and describe the difference between the two pieces of code, in terms of the different execution times we have discussed during the lecture. Then, try out running both snippets, explain what happens and the difference.
Coding exercises:
-
As mentioned, a drawback of HOAS interfaces is that the names chosen by the user are lost. Consider the following interface for the untyped lambda calculus:
trait Term case class HOASFun(funBody: Term => Term, variableName: String) extends Term case class HOASApp(fun: Term, arg: Term) extends Term
Try writing a macro
def fun(f: Term => Term): HOASFun = macro ...
that, when invoked as
fun(strangeName => body...)
, expands toHOASFun(strangeName => body..., "strangeName")
by capturing the name ("strangeName"
) of the parameter (strangeName
) of the argument (strangeName => body
) for the macro invocation.