Skip to main content

Metaprogramming in Scala 2 & 3

Picture of Jan Chyb, Scala 3 and Tooling Specialist

Jan Chyb

Scala 3 and Tooling Specialist
Sep 22, 2022|15 min read
Metaprogramming_in_Scala_2_&_3_image-min.jpg
1// macros.scala
2import scala.language.experimental.macros
3import scala.reflect.macros.whitebox.Context
4
5import example.util.NonZeroNumber
6
7object Macros {
8 def nonZeroNum(number: Int): NonZeroNumber = macro nonZeroNum_impl
9
10 def nonZeroNum_impl(
11 c: Context
12 )(number: c.Expr[Int]) = {
13 import c.universe._
14
15 // unpack a static value from the method argument
16 val Literal(Constant(constValue: Int)) = number.tree
17
18 // construct a NonZeroNumber based on that value
19 if (constValue > 0) {
20 q"_root_.example.util.Positive($number)"
21 } else if (constValue < 0) {
22 q"_root_.example.util.Negative($number)"
23 } else {
24 c.abort(c.enclosingPosition, "Non zero value expected")
25 }
26 }
27}
1// example/util/NonZeroNumber.scala
2package example.util
3sealed trait NonZeroNumber
4case class Positive(value: Int) extends NonZeroNumber
5case class Negative(value: Int) extends NonZeroNumber
1// main.scala
2object Main {
3 def main(args: Array[String]): Unit = {
4 println(Macros.nonZeroNum(5)) // Positive(5)
5 println(Macros.nonZeroNum(15)) // Positive(15)
6 println(Macros.nonZeroNum(-5)) // Negative(-5)
7 // println(Macros.nonZeroNum(0)) // will cause a compile time error
1inline def method() =
1inline def method(inline value: Int) =
1inline def nonZeroNum(inline value: Int): NonZeroNumber = {
2 inline if (value > 0) {
3 Positive(value)
4 } else inline if (value < 0) {
5 Negative(value)
6 } else {
7 scala.compiletime.error("Non zero value expected")
8 }
9}
1transparent inline def nonZeroNum(inline value: Int): NonZeroNumber = {
2 inline if (value > 0) {
3 Positive(value)
4 } else inline if (value < 0) {
5 Negative(value)
6 } else {
7 scala.compiletime.error("Non zero value expected")
8 }
9}
1// macros.scala
2import example.util._
3import scala.quoted._
4
5object Macros {
6 transparent inline def nonZeroNum(inline value: Int): NonZeroNumber =
7 ${ Macros.nonZeroNumImpl('value) }
8
9 def nonZeroNumImpl(using Quotes)(value: Expr[Int]): Expr[NonZeroNumber] = {
10 // unpack a static value from the method argument
11 val constValue = value.valueOrAbort
12
13 // construct a NonZeroNumber based on that value
14 if (constValue > 0) {
15 '{ Positive($value) }
16 } else if (constValue < 0) {
17 '{ Negative($value) }
18 } else {
19 quotes.reflect.report.errorAndAbort("Non zero value expected")
20 }
21 }
22}
1import scala.quoted._
2
3object Macros {
4 transparent inline def cnf(
5 inline cnfSpec: String, inline booleans: Boolean*
6 ): Boolean =
7 ${ cnfImpl('cnfSpec, 'booleans) }
8
9 def cnfImpl(using Quotes)(
10 cnfSpec: Expr[String], booleanSeqExpr: Expr[Seq[Boolean]]
11 ): Expr[Boolean] = {
12
13 // "unpack" Expr[Seq[T]] to Seq[Expr[T]]
14 val Varargs(booleans: Seq[Expr[Boolean]]) = booleanSeqExpr
15
16 // parse cnf string
17 case class Literal(negated: Boolean, refNum: Int)
18 val cnfStr = cnfSpec.valueOrAbort
19 val clauseStrs = cnfStr.split('^')
20 val clauses: Array[Array[Literal]] = clauseStrs
21 .map(num => num.substring(1, num.size - 1))
22 .map(_.split('v').map(literalStr =>
23 if (literalStr.startsWith("-")) Literal(negated=true, literalStr.substring(1).toInt)
24 else Literal(negated=false, literalStr.toInt)
25 ))
26
27 // finally, map the data to code via quoting and splicing
28 clauses.map { clause =>
29 clause.foldLeft('{false}){ (acc: Expr[Boolean], literal: Literal) =>
30 if (literal.negated) '{ $acc || !${booleans(literal.refNum)} } // Expr[Boolean]
31 else '{ $acc || ${booleans(literal.refNum)} } // Expr[Boolean]
32 }
33 }.foldLeft('{true}){ (acc: Expr[Boolean], booleanClause: Expr[Boolean]) =>
34 '{ $acc && $booleanClause } // Expr[Boolean]
35 }
36 }
37}
1def example(c: Context) = {
2 import c.universe._
3 val customAst: tree = {
4 // create a custom AST tree
5 }
6
7 q"""
8 code
9 ${customAst}
10 code
11 """
12}
1def example(using Quotes) = {
2 '{
3 code
4 ${customAst()}
5 code
6 }
7}
8
9def customAst(using Quotes) = { // different Quotes instance than in the example method
10 import quotes.reflect._
11 // create a custom program tree
12}

Subscribe to our newsletter and never miss an article