Scalaの勉強してたら嵌った。挙動が良く分からない。
Scala使いには常識あるいは仕様読めwな話かもしれないけど。
実用性の話ではなくてパズル的な話です。
Scalaのバージョンは2.9.1
文字列をくっつける
引数a1とa2を取って文字列として連結したい。a1の型はなるべく広く取りたい。
その1。引数1に+(x:String):String
というメソッドがあれば呼べる。
def concat1(a1:{def +(x:String):String}, a2:String) = { a1 + a2 } println(concat1(1, " inch")); // 1 inch println(concat1("1", "2")); // 12
その2。Anyで受けてtoStringして連結
def concat2(a1:Any, a2:String) = { a1.toString + a2 } println(concat2(1, " inch")); // 1 inch println(concat2("1", "2")); // 12
その3。引数1にtoString:String
というメソッドがあれば呼べる。
def concat3(a1:{def toString:String}, a2:String) = { a1.toString + a2 } println(concat3(1:java.lang.Integer, " inch")); // 1 inch println(concat3("1", "2")); // 12 // println(concat3(1, " inch")); // これはコンパイルエラーになる
最後のはみなし型を指定しないとエラーになる。こういう理由らしい:
[error] Note: an implicit exists from scala.Int => java.lang.Integer, but [error] methods inherited from Object are rendered ambiguous. This is to avoid [error] a blanket implicit which would convert any scala.Int to any AnyRef. [error] You may wish to use a type ascription: `x: java.lang.Integer`.
多重継承的な問題というか。
整数を掛ける
引数a1とa2を取ってかけ算したい。a1の型はなるべく広く取りたい。
その1。引数1に*(n:Int):Int
というメソッドがあれば呼べる。と思ったら呼べない。
def mul1(a1:{def *(n:Int):Int}, a2:Int) = { a1 * a2 } println(mul1(2, 3));
下のようにコンパイルエラーになる
[error] ...(略).../Hoge.scala:25: type mismatch; [error] found : Int(2) [error] required: AnyRef{def *(n: Int): Int} [error] println(mul1(2, 3)); [error] ^
???*1
その2。引数1にtoInt:Int
というメソッドがあれば呼べる。
def mul2(a1:{def toInt:Int}, a2:Int) = { a1.toInt * a2 } println(mul2(2, 3));
でも実行するとエラーになる
[error] (run-main) java.lang.NoSuchMethodException java.lang.NoSuchMethodException at scala.runtime.BoxesRunTime.toInteger(Unknown Source) at Hoge$.mul2$1(Hoge.scala:28) at Hoge$.main(Hoge.scala:30) at Hoge.main(Hoge.scala) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) java.lang.RuntimeException: Nonzero exit code: 1 at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:66)
これの一番下のところでエラーになっているっぽい。
自分でIntに変換してやれば問題ない。
def mul3[A](a1:A, a2:Int)(implicit f:A=>Int) = { f(a1) * a2 } implicit def doubleToInt(d:Double):Int = d.toInt implicit def stringToInt(s:String):Int = Integer.parseInt(s) println(mul3(2, 3)); // 6 println(mul3(2.2, 3)); // 6 println(mul3("2", 3)); // 6
まあ普段こんなことすることないので困ることも無いと思うけど。
追記
上のエラー
[error] ...(略).../Hoge.scala:25: type mismatch; [error] found : Int(2) [error] required: AnyRef{def *(n: Int): Int} [error] println(mul1(2, 3)); [error] ^
見た通り、構造的部分型はAnyRefベースなのでIntからは変換出来ないということらしい。
Anyベースで定義するか
def mul1(a1:Any{def *(n:Int):Int}, a2:Int) = { a1 * a2 } println(mul1(2, 3)) // 6
implicitで変換してやれば
def mul1(a1:{def *(n:Int):Int}, a2:Int) = { a1 * a2 } implicit def toAny(n:Int):AnyRef { def *(n:Int):Int } = new AnyRef { def *(m:Int):Int = n * m } println(mul1(2, 3)) // 6
AnyVal系でも構造的部分型が使えるっぽい。
追記2
単にいろいろな型を取って*がしたいだけなら、implicitでNumericを取って利用すれば良いみたい(Listのproductの真似)
def mul4[A](a1:A, a2:A)(implicit nm:Numeric[A]) = { nm.times(a1, a2) } println(mul4(2, 3)); // 6 println(mul4(2.1, 3)); // 6.300000000000001
*1:追記参照