なんかいまさらこんなタイトルでブログ書くと、すっごくマニアックなものを見つけたのか!?とか思われそうだけど、いやいや、すっごく初歩的な内容ですすみません(汗)
Scalaには結構慣れて来た方で、ソースコードは随分読めるようになってきたのね(上達はしていない)。
だけど、いまいち理解が進んでいないものも多い。その一つがこのimplicit。
なんとなくわかるんだけど、ちゃんと分かっていない。それは大変危険でダメのこと。
なので、しっかり理解するためにも、ブログにアウトプットすることでしっかり覚えようと思う。
後で読み返してもいいし。
Version
試したScalaのVersionは2.11.4デス。
>scala Welcome to Scala version 2.11.4 (Java HotSpot(TM) Client VM, Java 1.7.0_67). Type in expressions to have them evaluated. Type :help for more information.
implicitとは
発音記号「ɪmplísɪt」 【形容詞】 暗に示された,言わず語らずの,暗黙の (⇔explicit)
つまり、「暗黙の~」という意味ですかね。
3つのimplicit
Scalaでimplicitとつくものには、大きく分けて下記の3つがある(と思っている)。
- implicit parameter
- implicit conversion
- implicit classes(Scala ver.2.10~)
implicit parameter(暗黙引数)
例えば値を受け取ってそのまま返却するメソッドがあるとする。
scala> def func(i: Int) = i
func: (implicit i: Int)Int
当たり前だけど、このメソッドfuncを利用する際は引数を指定する必要がある。
scala> func <console>:9: error: missing arguments for method func; follow this method with `_' if you want to treat it as a partially applied function func ^ scala> func(1) res1: Int = 1
このメソッドの引数にimplicitをつけて、同じくimplicitがついた値を定義しておく。
scala> def func(implicit i: Int) = i func: (implicit i: Int)Int scala> implicit var implicitParam: Int = 50 implicitParam: Int = 50
すると、メソッド呼び出しの際implicitをつけた引数を省略してメソッドを呼び出す事が出来るようになり、下記の通りの結果になる。
※引数を指定しない場合はスコープの中から同じ型のimplicitな変数を探しに行き、implicitな引数にセットする。
scala> func(10) res4: Int = 10 scala> func res5: Int = 50 // ここでは上で定義した”implicitParam”の値(50)が引数にセットされた。
ルールとしては
などなどかな。
こんな感じ?
// 通常 def func1(i: Int, implicit j: Int) = i + j // × def func2(implicit i: Int, implicit j: Int) = i + j // × def func3(implicit i: Int, j: Int) = i + j // ○ // カリー化後 def func4(implicit i: Int)(implicit j: Int) = i + j // × def func5(implicit i: Int)(j: Int) = i + j // × def func6(i: Int)(implicit j: Int) = i + j // ○ // 同じスコープだとして implicit var implicitParam1: Int = 100 // × implicit var implicitParam2: Int = 200 // × // 関数リテラルはダメだった val func = (implicit i: Int) => {i}
ちょっと分からない動き その1「すべての引数にimplicitが付与される」
func3定義時に、下記の様にそれぞれの引数にimplicitが付いた。
呼び出し方もこのくらいだった。この動きは気をつけないとハマりそう。
scala> def func3(implicit i: Int, j: Int) = i + j func3: (implicit i: Int, implicit j: Int)Int scala> func3 res9: Int = 100 scala> func3(1,1) res10: Int = 2
ちょっと分からない動き その2「func5が定義出来ないのは何故だ」
何故func5が定義出来ないのかが分からない。
とりあえずfunc6の動きを確認する場合、こんな感じでしょう。 これは理解できる。
scala> func6(1) res24: Int = 51 scala> func6(1)(1) res25: Int = 2 scala> val func7 = func6(1) res1: Int = 51 scala> def func6(i: Int)(implicit j: Int, k:Int) = i + j + k func6: (i: Int)(implicit j: Int, implicit k: Int)Int // func3と同じく、implicitのついた方の引数はすべてimplicitが付与された
そしてfunc5については、下記のような感じでイメージして利用できると思ったんだけどなあと。
scala> func5 // 引数”implicit i: Int”に50が設定され、"res1: (j: Int)Int"が返却されるイメージだった(返却のイメージあってるのかな scala> func5(1)(1) // 引数”implicit i: Int”のimplicitを無視して、"res1: Int = 2"が返却されるイメージだった
(2015/03/09 追記)→意味がわかった
ちょっと分からない動き その1「すべての引数にimplicitが付与される」
まず、implicitが1番目の変数にしかつけられないのではなく、変数すべてに作用すると言う意味で、引数リストの頭にimplicitと定義するから、これはクリア。
ちょっと分からない動き その2「func5が定義出来ないのは何故だ」
こちらは、”最後の引数リスト”にしかimplicitをつけられないというルールがあるから。
こちらを参考にさせていただきました。
Scalaのimplicit(暗黙)入門 - seratch's weblog in Japanese
こういう勘違いは怖い怖い。気付いて良かった。
implicit conversion(暗黙変換)
自動で型変換を行ってくれるメソッドを定義することが出来る。
定義されたimplicitメソッドは、スコープの中で生きる。
例えば、Int型をString型に変換するようなimplixit defを用意した場合、以降Int型の数値に対してStrringのメソッドを呼び出せる。
これは面白い。
implicit def intConvertToString(x: Int): String = x.toString println(0.length()) // 1 println(1000.length()) // 4 println(1200.contains(20)) // true println(1200.contains("20")) // true
下記の場合、”intConvertToString3”は”intConvertToString1”と被るので定義出来ないとのこと。
implicit def intConvertToString1(x: Int): String = x.toString // ○ implicit def intConvertToString2(x: Int): Long = x.toLong // ○ implicit def intConvertToString3(x: Int): String = x.toString // ×
implicit classes(暗黙のクラス)
既存のクラスを拡張出来るような機能で、Scala2.10.0で追加される前まではPim My Libraryと言うものを利用していた。
たとえばこういう暗黙のクラスを用意しておけば
implicit class AddString(target: String) { def addPrefix = s"PREFIX_${target}" def addSuffix = s"${target}_SUFFIX" def addPrefixAndSuffix = s"PREFIX_${target}_SUFFIX" }
Stringで下記の様に呼び出せるのだ。
implicitクラスの引数に指定した方(ここではtarget:StringのString)を拡張して、独自のメソッドが定義出来る。
println("TEST".addPrefix) // PREFIX_TEST println("TEST".addSuffix) // TEST_SUFFIX println("TEST".addPrefixAndSuffix) // PREFIX_TEST_SUFFIX
ちなみに、先ほどのimplicit conversionとコラボ(型にimplicit classのAddStringを指定)してみたら、なんかすごいことになった。 まあ、Intを受け取って、toStringした結果を、独自のimplicit classの型(AddString)で返却してるだけなんだけど。
implicit def intConvertToString(x: Int): AddString = x.toString println(0.addPrefix) // PREFIX_0 println(0.addSuffix) // 0_SUFFIX println(0.addPrefixAndSuffix) // PREFIX_0_SUFFIX
ここまでやるとわけわからなくなりそうなので、これはあまり使わない方向でいこう。
ただ、こんな書き方が出来るのはとても面白い。
やっぱScala面白い。
その他
間違った箇所などあったらご指摘いただけると嬉しいデス!
参考
主に下記を参考にさせていただきました。
暗黙のパラメーター(implicit parameter) - Scalaメモ(Hishidama's Scala Memo) - Ne
暗黙変換(implicit conversion) - Scalaメモ(Hishidama's Scala Memo) - Ne
Scala 2.10.0 M3の新機能を試してみる(2) - SIP-13 - Implicit classes - kmizuの日記
暗黙のクラス(implicit classes) - Scalaメモ(Hishidama's Scala Memo) - Ne