CLOVER🍀

That was when it all began.

Scala 2.10.0 Value Classes

とりあえず、以下のNew featuresから順に興味のあるものを試していってみようと思います。
http://www.scala-lang.org/node/27499

まあ、基本他の方々のおっかけですよね…。

まずはValue Classes( and Universal Traits)から。

Value Classes

ドキュメント
http://docs.scala-lang.org/overviews/core/value-classes.html

素晴らしいことに、もう日本語訳が!
http://docs.scala-lang.org/ja/overviews/core/value-classes.html

だいたいこれを読めばわかるのですが、要は

  • AnyValのサブクラスを作成できる
  • publicなvalをひとつだけ持てる
  • (コンパイル時にインライン展開されるので、)実行時のオブジェクトの割り当てを回避できる

というものだそうで。

制約とかも載っているのであんまり書くのもあれですが、以下のような場合にインスタンスが生成されるんだよってことは注意した方がいいのかな?

  • 値クラスが別の型として扱われるとき
  • 値クラスが配列に代入されるとき
  • パターンマッチングなどにおいて、実行時の型検査を行うとき

とりあえず、サンプルを。
ValueClasses.scala

class IntWrapper(val underlying: Int) extends AnyVal {
  def show(): Unit = println(s"This Value = [$underlying]")
}

object ValueClasses {
  def main(args: Array[String]): Unit = {
    val iw = new IntWrapper(10)
    iw.show()
  }
}

IntWrapperがValue Classです。

これをコンパイルして実行すると

$ scalac ValueClasses.scala
$ scala ValueClasses
This Value = [10]

となります。

この時のコンパイル途中の結果をちょっと覗いてみます。「-Xprint:posterasure」が、インライン展開を終えた後になるようです。

$ scalac -Xprint:posterasure ValueClasses.scala 
[[syntax trees at end of               posterasure]] // ValueClasses.scala
package <empty> {
  final class IntWrapper extends Object {

  〜省略〜

  object ValueClasses extends Object {
    def <init>(): ValueClasses.type = {
      ValueClasses.super.<init>();
      ()
    };
    def main(args: Array[String]): Unit = {
      val iw: Int = 10;
      IntWrapper.this.show$extension(iw)
    }
  }
}

なるほど、確かにインライン展開されてますね。

    def main(args: Array[String]): Unit = {
      val iw: Int = 10;
      IntWrapper.this.show$extension(iw)
    }

Universal Traits(汎用トレイト)

何か見慣れないキーワードなのですが、以下のもののようです。

  • Anyを継承するトレイト
  • メンバーはdefのみ持てる(valとか宣言すると、コンパイルエラー)
  • 初期化は一切行わない

Value ClassにトレイトをMix-inすることができるようなのですが、それは汎用トレイトに限られますよと。

まんまですが、こんなやつです。

trait Printable extends Any {
  def print(): Unit = println(this)
}

こんなトレイトであれば、Value ClassにMix-inできますよ、と。

class IntWrapper(val underlying: Int) extends AnyVal with Printable {
  def show(): Unit = println(s"This Value = [$underlying]")
}

こんな感じにトレイトから「extends Any」を外すと

trait Printable {
  def print(): Unit = println(this)
}

class IntWrapper(val underlying: Int) extends AnyVal with Printable {
  def show(): Unit = println(s"This Value = [$underlying]")
}

以下のように「継承関係がおかしいよ!AnyValはObjectのサブクラスじゃないぞ!」と怒られます。

ValueClasses.scala:5: error: illegal inheritance; superclass AnyVal
 is not a subclass of the superclass Object
 of the mixin trait Printable
class IntWrapper(val underlying: Int) extends AnyVal with Printable {
                                                          ^
one error found

で、今度はこんな感じに修正して
ValueClasses.scala

trait Printable extends Any {
  def print(): Unit = println(this)
}

class IntWrapper(val underlying: Int) extends AnyVal with Printable {
  def show(): Unit = println(s"This Value = [$underlying]")
}

object ValueClasses {
  def main(args: Array[String]): Unit = {
    val iw = new IntWrapper(10)
    iw.show()
    iw.print()  // <- ここ、追加
  }
}

コンパイル。

$ scalac -Xprint:posterasure ValueClasses.scala
[[syntax trees at end of               posterasure]] // ValueClasses.scala
package <empty> {
  abstract trait Printable extends Object {
    def print(): Unit
  };
  final class IntWrapper extends Object with Printable {

  〜省略〜

  object ValueClasses extends Object {
    def <init>(): ValueClasses.type = {
      ValueClasses.super.<init>();
      ()
    };
    def main(args: Array[String]): Unit = {
      val iw: Int = 10;
      IntWrapper.this.show$extension(iw);
      new IntWrapper(iw).print()
    }
  };
  abstract trait Printable$class extends Object with Printable {
    def /*Printable$class*/$init$(): Unit = {
      ()
    };
    def print(): Unit = scala.this.Predef.println(Printable$class.this)
  }
}

確かにトレイトのメソッドを使用する時に、インスタンスを作成するようになっていますね。

    def main(args: Array[String]): Unit = {
      val iw: Int = 10;
      IntWrapper.this.show$extension(iw);
      new IntWrapper(iw).print()
    }

スクリプトでは使えない?

Value Classの制限のひとつに、

  • トップレベルクラスか静的にアクセス可能なオブジェクトのメンバである必要がある

というものがあり、

object Outer {
  class Inner(val x: Int) extends AnyVal
}

というような表記はOKだよ(objectのメンバはstaticになるので)、というのがあります。

が、scalaコマンドで直接実行するような形式の場合はValue Classの利用は不可のようです。

例えば、以下のようなスクリプトを用意して
value_classes_script.scala

class IntWrapper(val underlying: Int) extends AnyVal

object Outer {
  class Inner(val x: Int) extends AnyVal
}

scalaコマンドで実行すると、エラーになります。

$ scala value_classes_script.scala 
/xxxxx/value_classes_script.scala:1: error: value class may not be a member of another class
class IntWrapper(val underlying: Int) extends AnyVal
      ^
/xxxxx/value_classes_script.scala:4: error: value class may not be a member of another class
  class Inner(val x: Int) extends AnyVal
        ^
two errors found

スクリプト形式の場合は、Mainというオブジェクトの中のmainメソッドの中に定義されることになるようなので、そりゃあそうだよね…という気もしますが。まあ、スクリプト形式でこの機能が使えなくて困る局面があるか?という話ですね。