( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

Scalaの抽象構文木(abstract syntax tree、AST)をグラフィカルに表示するオプション


「そんなASTで大丈夫か?」
f:id:yuroyoro:20101006140122p:image


ということで、ほとんどの言語はプログラムの内部表現として抽象構文木(abstract syntax tree、AST)を作ると思います。Scalaももちろんコンパイルするときに作ります。


で、Scalaコンパイラが生成してるASTを見るオプションがあります。


"-Xprint:<phase>"オプションと"-Ybrowse:<phase>"オプションです。


こんなコードがあったとして、

case class Cell[T](v:T) {
  // TがInt型の場合にのみ呼び出せる
  def increment(implicit ev:T =:= Int ):Cell[Int] = Cell( v + 1 )

  import java.text.SimpleDateFormat
  import java.util.Date
  // TがDateまたはそのサブタイプのときに呼び出せる
  def formatDate(implicit ev: T <:< java.util.Date ) =
    (new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")).format(v)

  import scala.collection.immutable.WrappedString
  // Tがimplicit conversion等でWrappedStringと見なせる場合に呼び出せる
  // toIntはWrappedStringが持つメソッドだが、コンパイルは通る
  def asInt(implicit ev: T <%< WrappedString) = v.toInt
}

object Main {
  def main(args:Array[String]) = {
    println( Cell(99).increment )
    println( Cell( new java.util.Date).formatDate )
    println( Cell( new java.sql.Timestamp( System.currentTimeMillis)).formatDate )
    println( Cell("123").asInt )
  }
}

こいつを"scalac -Xprint:typer Cell.scala"と-Xprintオプションをつけてコンパイルしてみると、このようにコンパイラがどんな変換をしているのかがわかるというわけです。

ozaki@yuroyoro-MacBook $ scalac -Xprint:typer Cell.scala                                         [~/sandbox/.../work/implicit] 
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
[[syntax trees at end of typer]]// Scala source: Cell.scala
package <empty> {
  @serializable case class Cell[T >: Nothing <: Any] extends java.lang.Object with ScalaObject with Product {
    <synthetic> def copy$default$1[T >: Nothing <: Any]: T @scala.annotation.unchecked.uncheckedVariance = Cell.this.v;
    <caseaccessor> <paramaccessor> private[this] val v: T = _;
    <stable> <caseaccessor> <accessor> <paramaccessor> def v: T = Cell.this.v;
    def this(v: T): Cell[T] = {
      Cell.super.this();
      ()
    };
    def increment(implicit ev: =:=[T,Int]): Cell[Int] = Cell.apply[Int](ev.apply(Cell.this.v).+(1));
    import java.text.SimpleDateFormat;
    import java.util.Date;
    def formatDate(implicit ev: <:<[T,java.util.Date]): java.lang.String = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Cell.this.v);
    import scala.collection.immutable.WrappedString;
    def asInt(implicit ev: <%<[T,scala.collection.immutable.WrappedString]): Int = ev.apply(Cell.this.v).toInt;
    <synthetic> def copy[T >: Nothing <: Any](v: T = v): Cell[T] = new Cell[T](v);
    override def hashCode(): Int = ScalaRunTime.this._hashCode(Cell.this);
    override def toString(): String = ScalaRunTime.this._toString(Cell.this);
    override def equals(x$1: Any): Boolean = Cell.this.eq(x$1).||(x$1 match {
      case (v: Any)Cell[Any]((v$1 @ _)) if v$1.==(v) => x$1.asInstanceOf[Cell[T]].canEqual(Cell.this)
      case _ => false
    });
    override def productPrefix: java.lang.String = "Cell";
    override def productArity: Int = 1;
    override def productElement(x$1: Int): Any = x$1 match {
      case 0 => v
      case _ => throw new java.lang.IndexOutOfBoundsException(x$1.toString())
    };
    override def canEqual(x$1: Any): Boolean = x$1.$isInstanceOf[Cell[T]]()
  };
  final object Main extends java.lang.Object with ScalaObject {
    def this(): object Main = {
      Main.super.this();
      ()
    };
    def main(args: Array[String]): Unit = {
      scala.this.Predef.println(Cell.apply[Int](99).increment(scala.this.Predef.=:=.tpEquals[Int]));
      scala.this.Predef.println(Cell.apply[java.util.Date](new java.util.Date()).formatDate(scala.this.Predef.conforms[java.util.Date]));
      scala.this.Predef.println(Cell.apply[java.sql.Timestamp](new java.sql.Timestamp(java.this.lang.System.currentTimeMillis())).formatDate(scala.this.Predef.conforms[java.sql.Timestamp]));
      scala.this.Predef.println(Cell.apply[java.lang.String]("123").asInt(scala.this.Predef.<%<.conformsOrViewsAs[java.lang.String, scala.collection.immutable.WrappedString]({
        ((s: String) => scala.this.Predef.wrapString(s))
      })))
    }
  };
  final <synthetic> object Cell extends java.lang.Object with ScalaObject {
    def this(): object Cell = {
      Cell.super.this();
      ()
    };
    case <synthetic> def unapply[T >: Nothing <: Any](x$0: Cell[T]): Option[T] = if (x$0.==(null))
      scala.this.None
    else
      scala.Some.apply[T](x$0.v);
    case <synthetic> def apply[T >: Nothing <: Any](v: T): Cell[T] = new Cell[T](v)
  }
}


"-Ybrowse"オプションだと、ASTをGUIで見ることができます。"scalac -Ybrowse:typer Cell.scala "とすると、


こんなん表示されます。
f:id:yuroyoro:20101006140124p:image


オプションで指定する<phase>は、"scalac -Xshow-phases"で見ることができます。

scalac -Xshow-phases
parser
namer
packageobjects
typer
superaccessors
pickler
refchecks
selectiveanf
liftcode
selectivecps
uncurry
tailcalls
specialize
explicitouter
erasure
lazyvals
lambdalift
constructors
flatten
mixin
cleanup
icode
inliner
closelim
dce
jvm
terminal


typerは、型付けが終わった段階で、たいていはこのくらいで充分でしょう。implicit conversionや型推論がどう解決されているか見るのに便利ですね。

scalaコンパイラについてはこの死霊に詳しい解説があります。
http://www.sts.tu-harburg.de/people/mi.garcia/ScalaCompilerCorner/


「大丈夫だ。問題ない。」
f:id:yuroyoro:20101006140123p:image