ぼくが昨日知った Scala の変態記法をお知らせします。
scala> trait A { def greeting(name: String): String }
defined trait A
// これは普通
scala> val a1: A = new A { def greeting(name: String): String = "Hello, " + name }
a1: A = $anon$1@5c0f79f0
scala> a1.greeting("Serval")
res0: String = Hello, Serval
// !!!?!?!?!??!?!??!!?!?
scala> val a2: A = { name: String => "Hello, " + name }
a2: A = $$Lambda$1050/812031404@629adce
scala> a2.greeting("Arai-san")
res1: String = Hello, Arai-san
// なにこれ!すごーい!
scala> val a3: A = "Hello, " + _
a3: A = $$Lambda$1051/777970377@55a609dd
scala> a3.greeting("Fennec")
res2: String = Hello, Fennec
abstract method が一つだけ定義された trait は、ラムダ式から変換できるらしい。 @xuwei_k 先生曰く、2.12 から入った機能とのこと。 SAM conversion と呼ばれている。
SAM Conversion
SAM (single abstract method) conversion の仕様は言語仕様に記述されている。適当に訳してみる。
http://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#sam-conversion
SAM conversion
型
(T1, ..., TN) => T
の式(p1, ..., pN) => body
は以下を満たすとき、期待される型S
に SAM変換可能である。
S
のクラスC
がシグネチャ(p1: A1, ..., pN: AN): R
の抽象メソッドm
を一つ宣言しているC
はm
の他に抽象値メンバーを宣言・継承していないS
のサブタイプU
であって、式new U { final def m(p1: A1, ..., pN: AN): R = body }
が正しく型付けされる(期待される型S
に合致する)こと- スコーピングのために、
m
が static メンバーとして扱われること(U
のメンバーがbody
のスコープ内に現れないこと)(A1, ..., AN) => R
が(T1, ..., TN) => T
のサブタイプであること(この条件を満たすことでS
の未知の型パラメータの型推論を行う)SAM の対象となる関数リテラルは、上記のインスタンス生成式にコンパイルされるとは限らないことに注意せよ。これはプラットフォーム依存である。
ここから以下のことが言える。
- もしクラス
C
がコンストラクタを定義しているなら、それはアクセス可能であり、唯一の空の引数リストを定義しなければならないC
はfinal
やsealed
であってはならない(単純のため、SAM変換がその sealed class と同じコンパイル単位で行えるかどうかを問わないことにする)m
はポリモーフィックであってはならないC
の未知の型パラメータの推論を行うことで、S
から完全型U
を導出できなくてはならない最後に、いくつかの実装上の都合による制約を課す(これらは将来のリリースで撤廃されるかもしれない)。
C
はネストしていたり、ローカルであってはならない(0引数コンストラクタとなるよう、環境をキャプチャしてはならない)C
のコンストラクタは implicit 引数リストを持ってはならない(これは型推論を簡略化する)C
は自分型を宣言してはならない(これは型推論を簡略化する)C
は@specialized
であってはならない
例
抽象メソッドが一つだけであれば、他にメソッドが生えていてもいいらしい。抽象メソッドの引数も1つである必要もない。
scala> trait B {
| def greeting(place: String, species: String, name: String): String
| def speak: String = "みゃみゃみゃみゃみゃ"
| }
defined trait B
scala> val b: B = { (p: String, s: String, n: String) => s"ここは${p}!わたしは${s}の${n}だよ"}
b: B = $anonfun$1@ed2f2f6
scala> b.greeting("ジャパリパーク", "サーバルキャット", "サーバル")
res7: String = ここはジャパリパーク!わたしはサーバルキャットのサーバルだよ
scala> b.speak
res8: String = みゃみゃみゃみゃみゃ
しかし、SAM変換可能なのは関数リテラル (p1, ..., pN) => body
だけであることに注意。一般の FunctionN インスタンスをSAM変換することはできない。
scala> trait C { def speak(): String }
defined trait C
scala> val c1: C = { () => "アライさんに任せるのだ!" }
c1: C = $$Lambda$1135/2085013955@7197b07f
scala> val fn: Function0[String] = { () => "ふえぇぇぇぇぇ!?" }
fn: () => String = $$Lambda$1136/1695195255@7ce29a2d
scala> val c2: C = fn
<console>:13: error: type mismatch;
found : () => String
required: C
val c2: C = fn
リリースノートによれば、これは Java 8 のライブラリを便利に呼び出すのに使えるそうだ。SAM変換によって、 Runnable のインスタンスを簡単に作ることができる。
参考文献
tayama0324
225Contribution