요새 스칼라 스터디를 하고 있는데…(스칼라 어려워요 흑흑흑, 전 스맹 T.T) 아주 여러가지 기능들이 있습니다. 그런데 첫 부분에 나오는 예제부터 머리속을 땡땡 때리는 경우가 있습니다. 간단한 예를 들자면, tuple의 파라매터가 22개 밖에 안되는 것은 실제로 tuple1 ~ tuple22 까지의 클래스가 있어서 처리된다는 것(tuple 은 다시 product 이라는 것을 상속받는…)
보통 우리가 언어를 처음 배울때 쓰는 첫 예제는… 반가워 세상입니다. 즉 Hello World! 를 출력하는 것이죠.
object HelloWorld { def main(args: Array[String]) { println("Hello, world!") } }
그런데 App 이라는 trait 를 상속받으면 다음과 같은 형태로 똑같이 동작이 됩니다.
object HelloWorld extends App { println("Hello, world!") }
사실 스칼라를 공부하는 사람이야 그냥 당연하게 넘어갈 수 있지만, 두 번째 예의 경우는 println 코드가 있는 부분은 생성자입니다. 그런데 “어떻게 저게 자동으로 실행이 되는거지?” 라는 의문이 생기게 됩니다.(안생기면 500원…), 그리고 args 도 사용할 수 있습니다.
그래서 안을 조금 파보니…
App Trait 는 다시 DelayedInit 라는 Trait를 상속받습니다. 먼저 App Trait 부터 살짝 보도록 하겠습니다.
trait App extends DelayedInit { /** The time when the execution of this program started, in milliseconds since 1 * January 1970 UTC. */ @deprecatedOverriding("executionStart should not be overridden", "2.11.0") val executionStart: Long = currentTime /** The command line arguments passed to the application's `main` method. */ @deprecatedOverriding("args should not be overridden", "2.11.0") protected def args: Array[String] = _args private var _args: Array[String] = _ private val initCode = new ListBuffer[() => Unit] /** The init hook. This saves all initialization code for execution within `main`. * This method is normally never called directly from user code. * Instead it is called as compiler-generated code for those classes and objects * (but not traits) that inherit from the `DelayedInit` trait and that do not * themselves define a `delayedInit` method. * @param body the initialization code to be stored for later execution */ @deprecated("The delayedInit mechanism will disappear.", "2.11.0") override def delayedInit(body: => Unit) { initCode += (() => body) } /** The main method. * This stores all arguments so that they can be retrieved with `args` * and then executes all initialization code segments in the order in which * they were passed to `delayedInit`. * @param args the arguments passed to the main method */ @deprecatedOverriding("main should not be overridden", "2.11.0") def main(args: Array[String]) = { this._args = args for (proc <- initCode) proc() if (util.Properties.propIsSet("scala.time")) { val total = currentTime - executionStart Console.println("[total " + total + "ms]") } } }
젤 아래의 main을 보면, 아 여기서 실행되겠구나 할것입니다. 쉽네하고 보다보면, 다시 이상해집니다. 분명히 main이 보통 entrypoint 일텐데…(실제로는 object이니 이것을 실행하는 부분이 있긴하겠죠.) 뭔가 initCode 라는 것에서 proc를 가져와서 이걸 실행시킵니다.
그 위의 delayedInit 함수를 보니, body가 넘어와서 initCode에 저장됩니다.(여기서 body는 람다라고 보시면 될듯합니다.)
그럼 다시 처음으로 여기서 main이 실행되는 건 알겠는데… App Trait를 상속받은 object의 생성자를 실행을 시켜주는 걸로 봐서 아마도 위의 proc 가 App Trait를 상속받은 object의 생성자일꺼라는 예상을 할 수 있게 됩니다. 그러나, 여전히 delayedInit을 호출해주는 녀석은 보이지 않습니다. 다시 App Trait 가 DelayedInit Trait를 상속받으니, 이걸 살펴보도록 하겠습니다.
trait DelayedInit { def delayedInit(x: => Unit): Unit }
악!!! 살펴볼 내용이 없습니다. 그냥 인터페이스만 정의가 되어있습니다. 그럼 뭔가 언어적으로 뭔가 해주지 않을까 싶습니다. 소스를 까보면 src/reflect/scala/reflect/internal/Definitions.scala 에서 다음 코드를 발견할 수 있습니다.
def delayedInitMethod = getMemberMethod(DelayedInitClass, nme.delayedInit)
해당 클래스에서 delayedInit를 뽑아내는 것 같습니다.
그리고 src/compiler/scala/tools/nsc/transform/Constructors.scala 를 보면 다음 코드가 있습니다.
private def delayedInitCall(closure: Tree) = localTyper.typedPos(impl.pos) { gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(closure.symbol.tpe, This(clazz)))) }
그리고 위의 delayedInitCall은 rewriteDelayedInit() 에서 사용하고 있습니다. delayedInitCall을 실제로
호출하게 됩니다. 즉 여기서 아까 delayedInit가 호출되면서 App Trait 의 initCode 쪽에 생성자를 넣어주는 것입니다. 그래서 실제로 App Trait 의 main에서 그걸 호출하게 되는거죠.
def rewriteDelayedInit() { /* XXX This is not corect: remainingConstrStats.nonEmpty excludes too much, * but excluding it includes too much. The constructor sequence being mimicked * needs to be reproduced with total fidelity. * * See test case files/run/bug4680.scala, the output of which is wrong in many * particulars. */ val needsDelayedInit = (isDelayedInitSubclass && remainingConstrStats.nonEmpty) if (needsDelayedInit) { val delayedHook: DefDef = delayedEndpointDef(remainingConstrStats) defBuf += delayedHook val hookCallerClass = { // transform to make the closure-class' default constructor assign the the outer instance to its pa> val drillDown = new ConstructorTransformer(unit) drillDown transform delayedInitClosure(delayedHook.symbol.asInstanceOf[MethodSymbol]) } defBuf += hookCallerClass remainingConstrStats = delayedInitCall(hookCallerClass) :: Nil } }
마지막으로 Constructors.scala 안에서 다시 rewriteDelayedInit를 실행합니다. 그래서 App Trait를 상속받을 경우 생성자에만 코드를 넣어두면 실행이 되는 것입니다.
뭐, 이게 맞는 플로우인지는 정확하게 보증은 못합니다. 저도 이제 막 스칼라를 공부하는 중이고, 아무리 봐도, 스칼라를 편안하게 쓰지는 못할듯 하네요 T.T 흑흑흑