Scalaの抽象型と暗黙の型変換/引数、パラメータ制約:スケーラブルで関数型でオブジェクト指向なScala入門(9)(2/3 ページ)
Scalaの特徴を紹介し、基本構文や関数、クラスなど、Scalaの基本的な機能について解説する入門連載
暗黙の型変換の基本ルール
ここで暗黙の型変換の基本ルールを説明します。
implicitが付与された定義のみ、暗黙の型変換に使用される
変換用関数にimplicitキーワードが付与されていなければ、暗黙の型変換でその関数が使用されることはありません。
暗黙型変換は単一の識別子としてスコープ内にあること
implicit関数が定義されていたとしても、それがスコープ内になければ適用されることはありません。
例えば、上記dateToString関数がどこかの別パッケージや別オブジェクト内に定義されている場合、単一の識別子(dateToString)として使えないので、そのままでは適用されません。そういった場合はimportで単一の識別子として使用できるようにする必要があります(※コンパニオンオブジェクトの中にimplicit定義がされている場合は例外として使用可能)。
暗黙変換は一度しか実行されない
暗黙の型変換によって変換された結果をさらに暗黙的に変換することはありません。
暗黙の型変換を適用できる関数が2つ以上あるときはエラーになる
先ほどのDateからStringへ変換する例でいうと、dateToStringと同じシグネチャを持つimplicit関数がもう1つ存在した場合に暗黙の型変換を期待すると、あいまいであるという理由でエラーになります。
型のチェックで問題なければ暗黙の型変換は行われない
ソースファイルに記述されたままの状態で型チェックを通る場合、暗黙の型変換が行われることはありません。
以上が暗黙の型変換における基本ルールです。暗黙の型変換を使用するときは予期しない型変換を防止するため、implicit関数を専用のオブジェクトに定義し、個別にimportするようにしましょう。
次は、引数指定を省略できる、暗黙の引数を紹介します。
暗黙の引数とは
暗黙の引数(implicit parameter)とは、引数の型に合わせた暗黙の値を用意しておくことで、メソッド呼び出し時の引数を省略できる仕組みです。構文は、以下のようになっています。
def メソッド名(implicit 引数名:型名) : 戻り値型名 = 関数内容
引数の前にimplicitキーワードを付けることで、その引数が暗黙の引数となります。implicitキーワードは引数リストの先頭の変数にしか付けられないので、下記のように複数の引数リストを持たせる形で使用されることが多いようです。
def メソッド名(引数名:型名…)…(implicit 引数名:型名) : 戻り値型名 = 関数内容
補足 複数の引数リスト
複数の引数リストとは、関数の引数を分けて記述する方法です。例えば、3つの引数をすべて足す、下記のようなsum関数があるとします。
def sum(x:Int,y:Int,z:Int):Int = x + y + z scala> sum(1,2,3) res1: Int = 6
これを複数の引数リストに分けると、下記のようになります。関数の呼び出し方も少し違っていて、「()」が3つ連なっています。
def sum(x:Int)(y:Int)(z:Int):Int = x + y + z scala> sum(1)(2)(3) res2: Int = 6
では、REPL上で暗黙のパラメータを使用してみましょう。まずは2番目の引数にString型のimplicitパラメータを受け取るgreeting関数を定義します。
def greeting(name:String)(implicit greet:String) = { println(greet + name) }
この関数を使ってみます。第1引数のみ値を指定しても暗黙的な値が見つからず、エラーになります。
scala> greeting("taro") <console>:9: error: could not find implicit value for parameter greet: String greeting("taro") ^
暗黙的な値がない場合は、引数を明示的に指定すれば問題なく使えます。
scala> greeting("taro")("good morning!") good morning!taro
それでは、implicitパラメータに暗黙的な値を設定してもらいましょう。
implicitな変数を用意します。
scala> implicit val hello:String = "hello!" hello: String = hello!
greeting関数を第1引数指定のみで呼び出してみます。今度はimplicitな変数を定義してあるので、greetingの第2引数に適用されます。
scala> greeting("taro") hello!taro
暗黙のパラメータの基本的な使い方は以上です。暗黙のパラメータはパラメータの型とスコープ内の型をマッチさせてパラメータを選択します。こちらも暗黙の変換と同じく予期しないパラメータの適用を防ぐため、暗黙のパラメータの型には「暗黙のパラメータ型として使用する」ことだけを目的とした特別な型を用意する方がいいでしょう。
暗黙の型変換を用いた「可視境界」
前回の記事では型パラメータの上限境界と下限境界を紹介しました。上限/下限境界とは、型パラメータに指定する型を制限したいときに使用する手法で、型パラメータを任意のクラスのサブクラスやスーパークラスに制限できます。
型パラメータの境界指定については実は、もう1つ指定方法があり、「可視境界(view bound)」と呼ばれる指定方法があります。
可視境界の指定方法
可視境界は以下のように指定します。
class X[A <% T]
これは、AはTのサブクラスか暗黙の型変換でAからTに変換可能な型(AをTとして扱える型)を条件とします。
可視境界を試してみよう
ではREPLでサンプルを動かしてみましょう。Zクラスでは「A <% X」と可視境界を指定しています。これは、「Zクラスの型パラメータに指定できる型はXのサブタイプまたは暗黙の型変換でXに変換できる型」という意味です。
class X class Y class Z[A <% X]
Zクラスをインスタンス化してみましょう。型パラメータにXクラスを指定している場合は当然問題ありません。Yクラスを指定した場合、現状ではYクラスをXクラスに変換できないため、エラーになります。
scala> val a = new Z[X] a: Z[X] = Z@273d6d53 scala> val a = new Z[Y] <console>:10: error: No implicit view available from Y => X. val a = new Z[Y] ^
YクラスからXクラスへ暗黙の型変換を行うための関数を用意しましょう。この関数に特に意味はありません。Xのインスタンスを返すだけです。
implicit def yTox(y:Y):X = new X
再度YクラスをZクラスの型パラメータに指定してインスタンス化してみます。
scala> val a = new Z[Y] a: Z[Y] = Z@246fee3a ^
今度はYクラスからXクラスへの暗黙の型変換を行う関数が定義されているので、問題なくインスタンス化できました。このように、可視境界では「その型として扱えるかどうか」で型パラメータとして指定できるかを判断します。
Copyright © ITmedia, Inc. All Rights Reserved.