昨日に続いて、今日はSIP-13 Implicit Classesです。
http://docs.scala-lang.org/sips/pending/implicit-classes.html
今までImplicit Conversionを使う上で面倒な定義を、楽にしてくれるもののようで。これまでは、確かに
- 変換するためのメソッド定義
- 変換先の型の定義
の両方が必要でしたからね。
ドキュメントには、
implicit class RichInt(n: Int) extends Ordered[Int] { def min(m: Int): Int = if (n <= m) n else m ... }
が
class RichInt(n: Int) extends Ordered[Int] { def min(m: Int): Int = if (n <= m) n else m ... } implicit final def RichInt(n: Int): RichInt = new RichInt(n)
とコンパイラが変換するんだよ!と書いてます。
まあ、ちょっと試してみましょう。
例えば、こんなソースをImplicit Classesを使って書き直すと
ImplicitConversionsExample.scala
// 以下のimport文を入れないと、Scala 2.10.0では警告が出ます import scala.language.implicitConversions class IntWrapper(val underlying: Int) { def twice: Int = underlying * 2 } object IntWrapper { implicit def int2wrapper(i: Int): IntWrapper = new IntWrapper(i) } import IntWrapper._ object ImplicitConversionsExample { def main(args: Array[String]): Unit = { println("twiced = " + 2.twice) } }
こうなるということ?
ImplicitClasses.scala
implicit class IntWrapper(val underlying: Int) { def twice: Int = underlying * 2 } object ImplicitClasses { def main(args: Array[String]): Unit = { println("twiced = " + (2.twice)) } }
ちょっとコンパイル。
$ scalac ImplicitClasses.scala ImplicitClasses.scala:1: error: `implicit' modifier cannot be used for top-level objects implicit class IntWrapper(val underlying: Int) { ^ one error found
お…?トップレベルでは、「implicit」修飾詞は使えないと仰ってる?
ちょっと考えて
implicit class RichInt(n: Int) extends Ordered[Int]
が
class RichInt(n: Int) extends Ordered[Int] implicit final def RichInt(n: Int): RichInt = new RichInt(n)
となるんだから…
こういうこと?
object ImplicitClasses { implicit class IntWrapper(val underlying: Int) { def twice: Int = underlying * 2 } def main(args: Array[String]): Unit = { println("twiced = " + (2.twice)) } }
$ scalac ImplicitClasses.scala $ scala ImplicitClasses twiced = 4
コンパイル通った、そして動いた。
ふーん、今まで
class IntWrapper(val underlying: Int) { def twice: Int = underlying * 2 } object IntWrapper { implicit def int2wrapper(i: Int): IntWrapper = new IntWrapper(i) } import IntWrapper._
みたいにimportで引っ張ってきた部分ってどうするんだろ?と思ってましたけど、そうなるんですか…。
ということは、何かしらのobjectのメンバーとしてImplicit Classを定義して、そのオブジェクトのメンバーとしてimportすればいいのかな?
ICwithPackage.scala
package foo { object IC { implicit class IntWrapper(val underlying: Int) { def twice: Int = underlying * 2 } } import foo.IC.IntWrapper package bar { class Bar { def twiceTwice(n: Int): Int = n.twice.twice } } } import foo.bar.Bar package fuga { object Main { def main(args: Array[String]): Unit = println("twiceTwiced = " + new Bar().twiceTwice(2)) } }
コンパイル、実行。
$ scalac ICwithPackage.scala $ scala fuga.Main twiceTwiced = 8
動きましたね。
import foo.IC._
みたいに、全部引っ張ってこなくてもいいみたい?
あとは気になるのは…
Value Classとの組み合わせ
下記のように、Implicit ClassとValue Classを組み合わせることで、Implicit Conversionをオーバーヘッドなく使えるんだそうで。
object ImplicitClasses { implicit class StringWrapper(val underlying: String) extends AnyVal { def star: String = s"***$underlying***" } def main(args: Array[String]): Unit = { println("Hello World".star) } }
アノテーションと一緒に使う
こんな感じにImplicit Classにアノテーションを付与すると
@bar
implicit class Foo(n: Int)
これは、以下の構文糖衣らしいです。
@bar implicit def Foo(n: Int): Foo = new Foo(n) @bar class Foo(n:Int)
ということは…
Bar.java
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Bar { }
みたいなファイルを作って、
WithAnno.scala
object WithAnno { @Bar implicit class IntWrapper(val underlying: Int) { def twice: Int = underlying * 2 } def main(args: Array[String]): Unit = println(2.twice) }
みたいに付けてコンパイルすると…
$ scalac WithAnno.scala
コンパイルエラーにならない(笑)。
しかも、展開されている…。
$ scalac -Xprint:jvm WithAnno.scala [[syntax trees at end of jvm]] // WithAnno.scala package <empty> { object WithAnno extends Object { @Bar implicit <synthetic> def IntWrapper(underlying: Int): WithAnno$IntWrapper = new WithAnno$IntWrapper(underlying); def main(args: Array[String]): Unit = scala.this.Predef.println(scala.Int.box(WithAnno.this.IntWrapper(2).twice())); def <init>(): WithAnno.type = { WithAnno.super.<init>(); () } }; @Bar implicit class WithAnno$IntWrapper extends Object { <paramaccessor> private[this] val underlying: Int = _; <stable> <accessor> <paramaccessor> def underlying(): Int = WithAnno$IntWrapper.this.underlying; def twice(): Int = WithAnno$IntWrapper.this.underlying().*(2); def <init>(underlying: Int): WithAnno$IntWrapper = { WithAnno$IntWrapper.this.underlying = underlying; WithAnno$IntWrapper.super.<init>(); () } } }
他にいろいろ試してみましたけど、コンパイルエラーになりませんね。
@Target({ElementType.TYPE, ElementType.METHOD})
と書かないとエラーになるんじゃないかなぁと思ったんですが、どうやらScalaコンパイラは(Javaで書いた)アノテーションの配置可能な場所まではチェックはしてないようですね…。