object MacroTestMain {
def main(args: Array[String]): Unit = {
val addValue = MacroDemo.add(10, 20)
println(s"add: $addValue")
}
}
설명
개발자의 이해 → main 함수에서 MacroDemo.add(10, 20) 을 호출하면 MacroDemo의 def add[T](num1: Int, num2: Int): Int = macro add_impl 구분을 호출하고 이를 실제 impl로 가서 수행하여 값을 가져온다.
실제로 MacroDemo를 컴파일하면 아래와 같은 바이트코드가 만들어진다.
//decompiled from MacroDemo$.class
package me.gordonlee.MacroDemo;
import scala.collection.immutable..colon.colon;
import scala.collection.immutable.Nil.;
import scala.reflect.api.Mirror;
import scala.reflect.api.TreeCreator;
import scala.reflect.api.TypeCreator;
import scala.reflect.api.Exprs.Expr;
import scala.reflect.api.Names.NameApi;
import scala.reflect.api.Trees.TreeApi;
import scala.reflect.api.Types.TypeApi;
import scala.reflect.macros.Universe;
import scala.reflect.macros.blackbox.Context;
public final class MacroDemo$ {
public static MacroDemo$ MODULE$;
static {
new MacroDemo$();
}
public Expr add_impl(final Context c, final Expr num1, final Expr num2) {
Universe $u = c.universe();
Mirror $m = c.universe().rootMirror();
final class $treecreator1$1 extends TreeCreator {
private final Expr num1$1$1;
private final Expr num2$1$1;
public TreeApi apply(final Mirror $m$untyped) {
scala.reflect.api.Universe $u = $m$untyped.universe();
return $u.Apply().apply($u.Select().apply(this.num1$1$1.in($m$untyped).tree(), (NameApi)$u.TermName().apply("$plus")), new colon(this.num2$1$1.in($m$untyped).tree(), .MODULE$));
}
public $treecreator1$1(final Expr num1$1$1, final Expr num2$1$1) {
this.num1$1$1 = num1$1$1;
this.num2$1$1 = num2$1$1;
}
}
final class $typecreator2$1 extends TypeCreator {
public TypeApi apply(final Mirror $m$untyped) {
scala.reflect.api.Universe $u = $m$untyped.universe();
return $m$untyped.staticClass("scala.Int").asType().toTypeConstructor();
}
public $typecreator2$1() {
}
}
return $u.Expr().apply($m, new $treecreator1$1(num1, num2), $u.TypeTag().apply($m, new $typecreator2$1()));
}
private MacroDemo$() {
MODULE$ = this;
}
}
//decompiled from MacroDemo.class
package me.gordonlee.MacroDemo;
import scala.reflect.ScalaSignature;
import scala.reflect.api.Exprs.Expr;
import scala.reflect.macros.blackbox.Context;
@ScalaSignature(
bytes = "\u0006\u0001\u0005\u0005r!B\u0003\u0007\u0011\u0003aa!\u0002\b\u0007\u0011\u0003y\u0001\"\u0002\f\u0002\t\u00039\u0002B\u0002\r\u0002\u0005\u0013\u0005\u0011\u0004C\u0003e\u0003\u0011\u0005Q0A\u0005NC\u000e\u0014x\u000eR3n_*\u0011Qa\u0002\u0006\u0003\u0011%\t\u0011bZ8sI>tG.Z3\u000b\u0003)\t!!\\3\u0004\u0001A\u0011Q\"A\u0007\u0002\r\tIQ*Y2s_\u0012+Wn\\\n\u0003\u0003A\u0001\"!\u0005\u000b\u000e\u0003IQ\u0011aE\u0001\u0006g\u000e\fG.Y\u0005\u0003+I\u0011a!\u00118z%\u00164\u0017A\u0002\u001fj]&$h\bF\u0001\r\u0003\r\tG\rZ\u000b\u00035\t\"2a\u0007\u0010!!\t\tB$\u0003\u0002\u001e%\t\u0019\u0011J\u001c;\t\u000b}\u0019\u0001\u0019A\u000e\u0002\t9,X.\r\u0005\u0006C\r\u0001\raG\u0001\u0005]Vl'\u0007B\u0003$\u0007\t\u0007AEA\u0001U#\t)\u0003\u0006\u0005\u0002\u0012M%\u0011qE\u0005\u0002\b\u001d>$\b.\u001b8h!\t\t\u0012&\u0003\u0002+%\t\u0019\u0011I\\=)\u0007\rac\u0007\u0005\u0002.i5\taF\u0003\u00020a\u0005A\u0011N\u001c;fe:\fGN\u0003\u00022e\u00051Q.Y2s_NT!a\r\n\u0002\u000fI,g\r\\3di&\u0011QG\f\u0002\n[\u0006\u001c'o\\%na2\f\u0014bH\u001c9u\r[5\u000bX3\f\u0001E\"AeN\u0006:\u0003\u0015i\u0017m\u0019:pc\u00111rgO 2\u0007\u0015bThD\u0001>C\u0005q\u0014aC7bGJ|WI\\4j]\u0016\f4!\n!B\u001f\u0005\t\u0015%\u0001\"\u0002KY<d\u0006\r\u0011)S6\u0004H.Z7f]R,G\rI5oAM\u001b\u0017\r\\1!e9\n\u0014G\f\u0019.\u001bbJ\u0013\u0007\u0002\f8\t\"\u000b4!J#G\u001f\u00051\u0015%A$\u0002\u0011%\u001c()\u001e8eY\u0016\f4!J%K\u001f\u0005Q\u0015$\u0001\u00012\tY9D\nU\u0019\u0004K5su\"\u0001(\"\u0003=\u000b!\"[:CY\u0006\u001c7NY8yc\r)\u0013KU\b\u0002%f\t\u0011!\r\u0003\u0017oQC\u0016gA\u0013V->\ta+I\u0001X\u0003%\u0019G.Y:t\u001d\u0006lW-M\u0002&3j{\u0011AW\u0011\u00027\u0006\tS.\u001a\u0018h_J$wN\u001c7fK:j\u0015m\u0019:p\t\u0016lwNL'bGJ|G)Z7pIE\"acN/bc\r)clX\b\u0002?\u0006\n\u0001-\u0001\u0006nKRDw\u000e\u001a(b[\u0016\f4!\n2d\u001f\u0005\u0019\u0017%\u00013\u0002\u0011\u0005$GmX5na2\fDAF\u001cgUF\u001aQe\u001a5\u0010\u0003!\f\u0013![\u0001\ng&<g.\u0019;ve\u0016\fTaH\u001cle^\fD\u0001J\u001cm[&\u0011QN\\\u0001\u0005\u0019&\u001cHO\u0003\u0002pa\u0006I\u0011.\\7vi\u0006\u0014G.\u001a\u0006\u0003cJ\t!bY8mY\u0016\u001cG/[8oc\u0011yrg\u001d;2\t\u0011:D.\\\u0019\u0004KU4x\"\u0001<\u001e\u0003}\u0010TaH\u001cysr\fD\u0001J\u001cm[F\u001aQE_>\u0010\u0003ml\u0012A\u0000\u0019\u0004Ki\\Hc\u0001@\u0002\u0006Q)q0!\b\u0002 A)\u0011\u0011AA\u000b79!\u00111AA\u0003\u0019\u0001Aq!a\u0002\u0005\u0001\u0004\tI!A\u0001d!\u0011\tY!!\u0005\u000e\u0005\u00055!bAA\ba\u0005A!\r\\1dW\n|\u00070\u0003\u0003\u0002\u0014\u00055!aB\"p]R,\u0007\u0010^\u0005\u0005\u0003/\tIB\u0001\u0003FqB\u0014\u0018bAA\u000ea\t9\u0011\t\\5bg\u0016\u001c\b\"B\u0010\u0005\u0001\u0004y\b\"B\u0011\u0005\u0001\u0004y\b"
)
public final class MacroDemo {
public static Expr add_impl(final Context c, final Expr num1, final Expr num2) {
return MacroDemo$.MODULE$.add_impl(var0, var1, var2);
}
}
위 에서 알수 있는 것은 우리가 add() 라고 했던 함수의 선언이 사라졌다는 것과 + 를 구현한 함수 본문이 apply 안으로 들어가서 번역되었다는 것이다.
이를 사용하는 사용처는 어떻게 되어 있을까?
//decompiled from MacroTestMain$.class
package me.gordonlee.Main;
import scala.Predef.;
import scala.collection.mutable.ArrayOps.ofRef;
import scala.runtime.BoxedUnit;
public final class MacroTestMain$ {
public static MacroTestMain$ MODULE$;
static {
new MacroTestMain$();
}
public void main(final String[] args) {
int addValue = 30;
.MODULE$.println((new StringBuilder(5)).append("add: ").append(addValue).toString());
}
private MacroTestMain$() {
MODULE$ = this;
}
}
//decompiled from MacroTestMain.class
package me.gordonlee.Main;
import scala.reflect.ScalaSignature;
@ScalaSignature(
bytes = "\u0006\u00015:Q\u0001B\u0003\t\u000211QAD\u0003\t\u0002=AQAF\u0001\u0005\u0002]AQ\u0001G\u0001\u0005\u0002e\tQ\"T1de>$Vm\u001d;NC&t'B\u0001\u0004\b\u0003\u0011i\u0015-\u001b8\u000b\u0005!I\u0011!C4pe\u0012|g\u000e\\3f\u0015\u0005Q\u0011AA7f\u0007\u0001\u0001\"!D\u0001\u000e\u0003\u0015\u0011Q\"T1de>$Vm\u001d;NC&t7CA\u0001\u0011!\t\tB#D\u0001\u0013\u0015\u0005\u0019\u0012!B:dC2\f\u0017BA\u000b\u0013\u0005\u0019\te.\u001f*fM\u00061A(\u001b8jiz\"\u0012\u0001D\u0001\u0005[\u0006Lg\u000e\u0006\u0002\u001b;A\u0011\u0011cG\u0005\u00039I\u0011A!\u00168ji\")ad\u0001a\u0001?\u0005!\u0011M]4t!\r\t\u0002EI\u0005\u0003CI\u0011Q!\u0011:sCf\u0004\"a\t\u0016\u000f\u0005\u0011B\u0003CA\u0013\u0013\u001b\u00051#BA\u0014\f\u0003\u0019a$o\\8u}%\u0011\u0011FE\u0001\u0007!J,G-\u001a4\n\u0005-b#AB*ue&twM\u0003\u0002*%\u0001"
)
public final class MacroTestMain {
public static void main(final String[] args) {
MacroTestMain$.MODULE$.main(var0);
}
}
main 함수에서 보는 것과 같이 아예 10, 20을 더한 30이라는 수로 선언되었다. (컴파일러의 최적화 영향인듯 하다)
object MacroTestMain {
def main(args: Array[String]): Unit = {
MacroDemo2.isEven(20)
MacroDemo2.isEven(21)
val input = Integer.parseInt(args.head)
MacroDemo2.isEven(input)
}
}
설명
add 함수와 동일하게 구성하였으나, 한 가지 다른 점이 있다. 바로 jar 파일을 실행할 때 넘겨받는 값(args)을 input으로 사용하게 한 것이다. 상수는 컴파일타임에 최적화되어 컴파일타임에 코드를 생략할 수 있으나, main의 실행인자로 받은 값은 컴파일타임엔 알 수 없기 때문에 최적화 할 수 없다.
이에 메인 함수는 아래와 같이 바이트 코드로 변환되었다.
//decompiled from MacroTestMain$.class
package me.gordonlee.Main;
import scala.Predef.;
import scala.collection.mutable.ArrayOps.ofRef;
import scala.runtime.BoxedUnit;
public final class MacroTestMain$ {
public static MacroTestMain$ MODULE$;
static {
new MacroTestMain$();
}
public void main(final String[] args) {
.MODULE$.println("even number");
BoxedUnit var10000 = BoxedUnit.UNIT;
.MODULE$.println("odd number");
var10000 = BoxedUnit.UNIT;
int input = Integer.parseInt((String)(new ofRef(.MODULE$.refArrayOps((Object[])args))).head());
if (input % 2 == 0) {
.MODULE$.println("even number");
var10000 = BoxedUnit.UNIT;
} else {
.MODULE$.println("odd number");
var10000 = BoxedUnit.UNIT;
}
}
private MacroTestMain$() {
MODULE$ = this;
}
}
//decompiled from MacroTestMain.class
package me.gordonlee.Main;
import scala.reflect.ScalaSignature;
@ScalaSignature(
bytes = "\u0006\u00015:Q\u0001B\u0003\t\u000211QAD\u0003\t\u0002=AQAF\u0001\u0005\u0002]AQ\u0001G\u0001\u0005\u0002e\tQ\"T1de>$Vm\u001d;NC&t'B\u0001\u0004\b\u0003\u0011i\u0015-\u001b8\u000b\u0005!I\u0011!C4pe\u0012|g\u000e\\3f\u0015\u0005Q\u0011AA7f\u0007\u0001\u0001\"!D\u0001\u000e\u0003\u0015\u0011Q\"T1de>$Vm\u001d;NC&t7CA\u0001\u0011!\t\tB#D\u0001\u0013\u0015\u0005\u0019\u0012!B:dC2\f\u0017BA\u000b\u0013\u0005\u0019\te.\u001f*fM\u00061A(\u001b8jiz\"\u0012\u0001D\u0001\u0005[\u0006Lg\u000e\u0006\u0002\u001b;A\u0011\u0011cG\u0005\u00039I\u0011A!\u00168ji\")ad\u0001a\u0001?\u0005!\u0011M]4t!\r\t\u0002EI\u0005\u0003CI\u0011Q!\u0011:sCf\u0004\"a\t\u0016\u000f\u0005\u0011B\u0003CA\u0013\u0013\u001b\u00051#BA\u0014\f\u0003\u0019a$o\\8u}%\u0011\u0011FE\u0001\u0007!J,G-\u001a4\n\u0005-b#AB*ue&twM\u0003\u0002*%\u0001"
)
public final class MacroTestMain {
public static void main(final String[] args) {
MacroTestMain$.MODULE$.main(var0);
}
}
첫 번째 20, 21처럼 상수를 넣은 결과는 println을 직접적으로 뽑아서 함수 본문으로 가져왔다.
다만 args를 가져온 값은 함수 본문 자체가 메인 함수로 그대로 복사-붙여넣기를 한 것처럼 가져와졌다.