型パラメータとimplicitに関連した機能をもう1つ紹介しましょう。暗黙の引数を利用すると、型パラメータが特定のクラスのときだけ呼び出せる制約を持ち、それをコンパイル時にチェックされるメソッドを作ることができます。これを「型パラメータの制約(Generalized Type Constraints)」といいます(※日本語での名称は特に決まっていません)。
この型パラメータ制約はいくつか種類があるので、それぞれ紹介します。
まずは、「A =:= B」というパラメータ制約です。これはAの型がBの型と等しければメソッドを呼び出せるという制約です。
下記サンプルをご確認ください。Xクラスは型パラメータを1つ受け取ります。受け取るパラメータは何を指定しても問題ありませんが、execメソッドを使用するためには、型パラメータがInt型でなければ使用できない制約になっています。
class X[A]{ def exec(implicit t:A =:= Int):Unit = println("exec") }
暗黙のパラメータを使用し、「=:=(Predefクラスに定義してある)」で型パラメータがどの型なのかをチェックします。実際にXクラスを使用してみましょう。型パラメータにInt型とString型、それぞれ指定して動作を確認してみます。
scala> val x = new X[Int] x: X[Int] = X@60813aca scala> x.exec exec scala> val x2 = new X[String] x2: X[String] = X@1e680525 scala> x2.exec <console>:10: error: Cannot prove that String =:= Int. x2.exec ^
型パラメータにInt型を指定した場合、execメソッドも問題なく使用できています。型パラメータにString型を指定した場合、Xクラスのインスタンス化は問題ありませんが、execメソッドを使用したところでエラーになります。
次は「A <:< B」と指定する制約です。これは、「AはBと同じ、もしくはサブクラスである」という制約です。Baseクラスとそれを継承したEx1クラス、それらに関係ないAnotherクラスを用意します。Yクラスでは型パラメータの制約をBaseかそのサブクラスに制限しています。
class Base class Ex1 extends Base class Another class Y[A] { def exec(implicit t:A <:< Base):Unit = println("exec") }
Yクラスをインスタンス化して使用してみます。型パラメータにEx1クラスとAnotherクラスを指定して、それぞれ確認してみましょう。
scala> val y = new Y[Ex1] y: Y[Ex1] = Y@21b6484c scala> y.exec exec scala> val y2 = new Y[Another] y2: Y[Another] = Y@16030e71 scala> y2.exec <console>:12: error: Cannot prove that Another <:< Base. y2.exec ^
Baseクラスを継承するEx1クラスを型パラメータに指定した場合は問題なくexecメソッドを使用できています。Baseクラスと関係のないAnotherクラスを指定した場合は、execメソッドを使用したところでエラーになっています。
最後は「A => B」という制約です。これは先ほどご紹介したの可視境界と意味は同じで、「Aは暗黙の型変換でBに変換できる(AをBとして扱える)」という制約です(※Scala2.9までは「A <%< B」と記述していましたが、現在はdeprecatedになっています)。
下記サンプルのZクラスでは、型パラメータの制約を「指定したクラスはYクラスとして扱えること」という制限しています。
class X class Y class Z[A]{ def exec(implicit t:A => Y):Unit = println("exec") }
Xクラスを型パラメータに指定してZクラスをインスタンス化してみましょう。
scala> val z = new Z[X] z: Z[X] = Z@2f1261b1 scala> z.exec <console>:12: error: No implicit view available from X => Y. z.exec ^
XクラスをYクラスに変換する暗黙の型変換関数はまだ用意していないので、execメソッドを使用しようとするとエラーになります。ではXクラスをYクラスに変換する暗黙の型変換関数を定義して、もう一度execメソッドを使用してみます。
implicit def xToy(x:X):Y = new Y scala> z.exec exec
今度は普通にexecメソッドを使えました。上限/下限境界や可視境界では、型パラメータに指定する型を制限できましたが、型パラメータ制約(Generalized Type Constraints)では、型パラメータに指定する型でなく、使えるメソッドを制限できます。
今回は型パラメータと同じ機能を実現できる抽象型から始まり、implicitキーワードを用いた暗黙の型変換と暗黙の引数、さらには型パラメータで指定する型の制限を掛けられる可視境界と型パラメータ制約を紹介しました。
暗黙の型変換や暗黙のパラメータは非常に便利で、うまく使えばコード量を削減して簡潔なプログラミングを可能にしてくれますが、思わぬバグの元になる危険性も持っています。暗黙の型変換/暗黙のパラメータはよく考えて使うようにしましょう。
中村修太(なかむら しゅうた)
クラスメソッド勤務の新しもの好きプログラマーです。昨年、東京から山口県に引っ越し、現在はノマドワーカーとして働いています。好きなJazzを聴きながらプログラミングするのが大好きです。
Copyright © ITmedia, Inc. All Rights Reserved.