うまく限定出来ないケースが見つかったためそれに対応するための 続<:<を使ってメソッド引数の型を限定する を書きました(2014.12.22)。
はじめに
型はなんでもいいのですが、例えばInt型,Boolean型,String型のいずれかを受け付けるfoo関数を定義したいとする。
案1 親クラス(trait)で型を宣言する
今回の場合、IntとBooleanとStringのみに共通する親クラスやtraitは存在しないのでこの作戦は使えない。
案2 オーバーロード
YES、出来る。
けどScalaさんならもっと違う書き方あるんでしょう?と勘ぐるくらいにはScalaに慣れてきた。
def foo(arg: Int) = println(arg)
def foo(arg: String) = println(arg)
def foo(arg: Boolean) = println(arg)
案3 <:<
見つけた。
def foo[T](arg: T)(implicit ev: Int with Boolean with String <:< T) = println(arg)
動作確認してみる。
foo(1) // 1
foo(true) // true
foo("hello") // hello
foo(2.3) // error: Cannot prove that Int with Boolean with String <:< Double.
Double型渡したらコンパイルエラーになった。成功!
ところで <:< ってなんだよ?
型だよ。
Predef.scalaで宣言されている。
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
- さっきのコンパイル時のエラーメッセージはimplicitで宣言された型に対するインスタンスが見つからないために出ていたメッセージだった
- <:<は型パラメータを2つ取るクラス。先程のは中置記法で書かれていた。
中置記法ってなんだよ?
型パラメータを2つとる場合、Scalaでは2つの書き方がある。
例えばMap。以下どちらの方法で書いてもコンパイルは通る。
// その1
def foo(m: Map[Int, String]) = ???
// その2
def foo(m: Int Map String) = ???
どういう仕組みだよ?
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]
これが全貌。
以降、話を簡単にするために、継承関係がイメージしやすいAnimal - Catを使う。
先の例を書き換えて以降foo関数とは以下のこととする。
def foo[T](arg: T)(implicit ev: Cat <:< T) = arg
foo関数にAnimal型のインスタンスを渡したとすると、implicit部は Cat <:< Animal
となる。 Cat <:< Animal
なインスタンスが見つかればOKだし見つからなければ could not find implicit value ...
というおなじみのエラーになる(今回の<:<の場合エラーメッセージを変えているので通常のこれではないが)。
インスタンスが返ってくるのはimplicit defで宣言されている$conforms。
コードを読むと実際にはインスタンス生成はしておらず唯一のインスタンスであるsingleton_<:<をキャストして返しているだけだ。
$conformsは型パラメータを1つしか取っていないので<:<の一つ目の型パラメータが渡ってくる。この例だと ev: Cat <:< T
なので1つ目はCat、つまり$conformsからは Cat <:< Cat
が返ってくる。
なお、探しているのは Cat <:< Animal
なのに Cat <:< Cat
が返ってきてもOKな理由は<:<の宣言部にある。
<:<は <:<[-From, +To]
と宣言されており、To部分は共変である。共変で馴染み深いのは List[+A]
だろう。
以下のようにList[Cat]はList[Animal]に代入可能。
val a: List[Animal] = List(new Cat)
つまり Cat <:< Animal
に Cat <:< Cat
は代入可能。よって Cat <:< Animal
は見つかった、ということになる。
追記
あ、もちろん、Tは<:<の1番目の型パラメータに指定しても大丈夫。
def foo[T](arg: T)(implicit ev: T <:< Animal) = arg