https://docs.scala-lang.org/overviews/macros/paradise.html
- 2.10.x
- 2.11.x
- 2.12.x
- 2.13.x (built-in in scalac)
#define sum(X, Y) X+Y
@Data public class DataExample {
private final String name;
}
- A lie of less code which can do more
- Macro should not be created in real world
- Most general purpose and useful macros has been created in the open source community
- Hard to maintain and hard to debug
- Painful migration for Scala 2 to Scala 3
- Typesafe: https://github.com/lightbend/scala-logging
- zhongl: https://github.com/hanabix/config-annotation
- Atry: https://github.com/ThoughtWorksInc/enableIf.scala
- Atry: https://github.com/ThoughtWorksInc/sbt-example
logger.debug(s"Some $expensive message!")
if (logger.isDebugEnabled) logger.debug(s"Some $expensive message!")
final class Logger private (val underlying: org.slf4j.Logger) {
def debug(message: String): Unit = macro LoggerMacro.debugMessage
}
message.tree match {
case q"scala.StringContext.apply(..$parts).s(..$args)" =>
val format = parts.iterator.map({ case Literal(Constant(str: String)) => str })
// Emulate standard interpolator escaping
.map(StringContext.processEscapes)
// Escape literal slf4j format anchors if the resulting call will require a format string
.map(str => if (args.nonEmpty) str.replace("{}", "\\{}") else str)
.mkString("{}")
val formatArgs = args.map(t => c.Expr[Any](t))
(c.Expr(q"$format"), formatArgs)
case _ => (message, Seq.empty)
}
@data
class A {
var x: Int = _
var y: String = _
}
val a = new A
a.setX(12)
assert(a.getX === 12)
a.setY("Hello")
assert(a.getY === "Hello")
We now have two demo macro project for:
- interpolate the AST of a scala method
- interpolate the AST of a scala class
@conf trait kafka {
val server = new {
val host = "wacai.com"
val port = 12306
}
val socket = new {
val timeout = 3 seconds
val buffer = 1024 * 64L
}
val client = "wacai"
}
trait kafka {
val server = new {
val host = config.getString("kafka.server.host")
val port = config.getInt("kafka.server.port")
}
val socket = new {
val timeout = Duration(config.getDuration("kafka.socket.timeout",
SECONDS))
val buffer = config.getBytes("kafka.socket.buffer")
}
val client = config.getString("kafka.client")
...
}
object XYZ {
@enableIf(classpathMatches(".*spark-catalyst_2\\.\\d+-3\\.2\\..*".r))
private def getFuncName(f: UnresolvedFunction): String = {
// For Spark 3.2.x
f.nameParts.last
}
@enableIf(classpathMatches(".*spark-catalyst_2\\.\\d+-3\\.1\\..*".r))
private def getFuncName(f: UnresolvedFunction): String = {
// For Spark 3.1.x
f.name.funcName
}
}