2018/04/18
Scalaの3つのimplicit
Scalaについて勉強した時のメモ(その5)です。Scalaのimplicitと呼ばれる系の機能について。
※ 元々はmarkdownで書いていたテキストの転載。
- Scala implicit修飾子 まとめ - Qiita
- implicit conversion, implicit class, implicit parameterがある。
- implicit: 暗黙の〜
- implicit修飾子を付けて定義した場合、コンパイラが適宜、必要なメソッドや型を探索して自動的に適用してくれる。
暗黙の型変換(implicit conversion)
- 型変換(キャスト)する関数をimplicitに定義しておくことで自動的にキャストしてくれる。
暗黙の型変換の定義。
implicit def d2i(d: Double):Int = d.toInt
implicit定義前。
scala> val x:Int = 3.14
<console>:11: error: type mismatch;
found : Double(3.14)
required: Int
val x:Int = 3.14
^
implicit定義後。
scala> val x:Int = 3.14
x: Int = 3
- 暗黙の型変換は推奨されていない/しない人が多い。
- 公式のドキュメントですら、"implicit conversions can have pitfalls"と書かれている。
- implicit conversionに対する否定的なコメント。
拡張メソッド(implicit class / 既存の型を拡張する)
- 既存の型を拡張し、メソッドを追加する。
- pimp my libraryパターンと言われる。
- ちなみに、C#やTypeScriptにも同名の類似した機能がある。
- レシーバにメソッドを生やす事ができるのが特徴。
String型に、空文字ならNone、文字があればSomeで値を包む関数を定義したい場合、以下のように拡張できる。
implicit class OptionString(str: String){
def opt(): Option[String] = if (str.isEmpty) None else Some(str)
}
これは、次のように使える。
scala> "".opt
res2: Option[String] = None
scala> "abc".opt
res3: Option[String] = Some(abc)
上記の場合、OptionString
の名前自体には特に意味はない。str: String
がレシーバを表している。 よりGenericに書きたい場合は、型パラメータを入れられる。
暗黙のパラメータ(implicit parameter)
- 暗黙に受け渡しされる引数。
- implicit修飾子により定義された変数を暗黙的にimplicit修飾子が付けられた引数に代入する。
- 単に暗黙的に引数を補完するだけだが、暗黙のパラメータを応用することで型クラスを実装することができる。
次のコードでは、二番目の引数をimplicitとしている。
scala> implicit val x = 20
x: Int = 20
scala> def f(a: Int)(implicit b: Int):Int = a + b
f: (a: Int)(implicit b: Int)Int
scala> f(1)
res4: Int = 21
- コンパイラが自動的に暗黙のパラメータを認識し、必要としている時に自動的にimplicitパラメータを探索する。
- (多分)静的型付言語に動的スコープを導入したような物(?)
- Implicit Parameters: Dynamic Scoping with Static Types というタイトルの論文がある。
- 関数を実行する場所やその瞬間によって値をコロコロ変えることが出来る。
- 暗黙のパラメータ自体は、Haskellの型クラスをエミュレートするために実装されたらしい。
- 引数の数が大量にある、または、同じような引数を取る関数を大量に呼び出している時などに有効と思われる。
- 割とホントに見えない所で代入が発生しているので、普段意識せずに使ってる人が多そうな機能の一つだと思われる(Futureなど)。
- 暗黙のパラメータに関するエラー
- 暗黙のパラメータを必要としているにもかかわらず、必要となるimplicitな変数が定義されていない場合はエラーが出る。
- あるクラスの関数を別のクラスの関数に移した時などに、この手のエラーがよく発生する。
- 移植元のimplicitな変数と移植先のimplicitの変数の何が違うかを考えると見つけやすい。エラーメッセージもヒントになる。
- 暗黙のパラメータによる複数の代入候補が存在する場合にもエラーが発生する。(一応優先順位はあるらしいが)
- 暗黙のパラメータを必要としているにもかかわらず、必要となるimplicitな変数が定義されていない場合はエラーが出る。
- 暗黙のパラメータによる代入を許したくない場合は、明示的に引数を指定することでそれを回避できる。前述の例で言うと、次のように書けば、暗黙のパラメータは回避される。
scala> f(1)(30)
res5: Int = 31
- implicit parameterを使用することで、型クラスを構成できる。(←ここが重要)