Scalaには、「implicit parameter」という機能があります。これは、メソッドに対して暗黙的にパラメーターを付与するものです。
この機能自体の説明は割愛します。暗黙的に知っているものとして以下の記事を書きます。
で、この機能、どうやらHaskellの型クラスに類する機能を実現するために仕組まれた孔明の罠・・・もとい、オダスキー先生の罠らしいのです。
以下証拠。
Scalaスケーラブルプログラミング(コップ本)第2版 045P 「Scalaの暗黙のパラメーターは、Haskellの型クラスに触発されたものだ。より古典的なオブジェクト指向的な設定のもとでは、両者は同じような効果を生む」
これは、和訳ですがオダスキー先生の声でしょう。何か持って回った言い回しなのが気になります。
「古典的なオブジェクト指向的設定のもと」で「同じような」効果を生むとあります。
完全に同じじゃないけど、結果的に得られる効果は一緒だからいいでしょ?的なニュアンスがありますね・・。
id:lyrical_logicalさんのブログです。
implicit conversion と type class
こちらは、implicit parameterについてというより、引用元となっているid:xuwei-kさんのスライドに対するツッコミみたいです。
元となっている、id:xuwei-kさんのスライドです。
HaskellとScala
ここでも、12Pで「implicit は、型クラスのための構文です(キリッ]と言いきっています。
実例としてFunctorが出てきていまして、HaskellとScalaが比較されているので非常にわかりやすいです。
だた、このスライドの場合、利用例が無いので、最後のところでイメージがぼんやりしてしまいました。
(Haskellの人にScalaとの対応を説明するのが主眼なので、このスライドが悪い分けではありません。念のため)
そこで、#rpscalaにて、質問してみました。
「implicit parameterってなに?|0_0|)??」
結果、教えて貰った資料が、これです。
Poor Man's Type Classes(PDF)
なるほど。わかりやすい。実行例も書いてあります。
あと、こちらはid:lyrical_logicalさんのスライドです。21P辺りから、implicit parameterの説明があります。
http://www.slideshare.net/lyrical_logical/scala-2
そうとは書いてないですが、用例などは、型クラスその物です。
・・・と、ここまで見てきて疑問というかモヤモヤがあります。
コードで説明するのが早いと思いますので、コードを書きます。
まずは、Haskellで型クラスを定義し、それを利用します。
class Hoge a where age :: a -> a sage :: a -> a hoge :: a -> a instance Hoge Integer where age a = a + 1 sage a = a - 1 hoge a = a * a instance Hoge Bool where age a = True sage a = False hoge = not num :: Integer -- 型推論のため(冗長ですがごめんなさい) num = 123 main = do print (age num) print (sage num) print (hoge num) print (age True) print (sage True) print (hoge True)
age sage hogeの挙動が、引数によって切り替わります。うれしいこととしては、色々な型に対するHogeのinstanceを後からどんどん追加していける事でしょうか。
続いて、同様の処理をScalaで書きます。まず、Hogeを定義します。
package com.qtamaki.typeclass trait Hoge[A] { def age(a: A): A def sage(a: A): A def hoge(a: A): A } object Hoge { implicit def IntHoge: Hoge[Int] = new Hoge[Int] { def age(a: Int) = a + 1 def sage(a: Int) = a - 1 def hoge(a: Int) = a * a } implicit def BoolHoge: Hoge[Boolean] = new Hoge[Boolean] { def age(a: Boolean) = true def sage(a: Boolean) = false def hoge(a: Boolean) = ! a } def runAge[A](a:A)(implicit ev: Hoge[A]): A = { ev.age(a) } def runSage[A](a:A)(implicit ev: Hoge[A]): A = { ev.sage(a) } def runHoge[A](a:A)(implicit ev: Hoge[A]): A = { ev.hoge(a) } }
そして、mainを書きます。
package com.qtamaki.typeclass import Hoge._ object Main { def main(args: Array[String]): Unit = { // defined by Library author println(runAge(123)) println(runSage(123)) println(runHoge(123)) println(runAge(true)) println(runSage(true)) println(runHoge(true)) // defined by Main author println(runAgeMain(123)) println(runSageMain(123)) println(runHogeMain(123)) println(runAgeMain(true)) println(runSageMain(true)) println(runHogeMain(true)) } def runAgeMain[A](a:A)(implicit ev: Hoge[A]): A = { ev.age(a) } def runSageMain[A](a:A)(implicit ev: Hoge[A]): A = { ev.sage(a) } def runHogeMain[A](a:A)(implicit ev: Hoge[A]): A = { ev.hoge(a) } }
ここで、implicit parameterを利用するメソッドを2回定義しているのは、わざとです。
haskellでは、このrunAgeやrunAgeMainに相当する関数は存在していません。
この辺が、オダスキー先生曰く『古典的なオブジェクト指向的設定のもと」で「同じような」効果を生む』という部分でしょうか。
確かに、オブジェクト指向では、レシーバーとなるオブジェクトインスタンスが必要となるので、それを明示的にしなければならず、暗黙的に運搬するために明示的に宣言した感じなのでしょうか。
そして、僕の思っているモヤモヤは、この利用メソッドを誰が書くのか?と言う事です。
カジュアルなユーザーは、"age"が呼べれば良いので、その人達に「おまじないだからimplicitパラメータを書け!」と言うのもアレな気がしますし、かと言って、ライブラリ側(Hoge側)にユーティリティメソッドを定義するのも、ライブラリ提供者からすると蛇足なきもします。
なんかスッキリしないな〜。と思った。| ´ - ω - ` |
追記
そもそも、
import Hoge._
なんていう、飛び道具をつかっているので、runAgeをスッキリ書けているが、本来これはこんな感じになるべき?
println(Hoge.runAge(123)) println(Hoge.runSage(123)) println(Hoge.runHoge(123))
こうなってくると、そもそも利用者が呼びたいのはage(Functorであればfmap)なので、こっちのユーティリティメソッド側をageとした方が良いということになるよね。
package com.qtamaki.typeclass trait Hoge[A] { def _age(a: A): A def _sage(a: A): A def _hoge(a: A): A } object Hoge { implicit def IntHoge: Hoge[Int] = new Hoge[Int] { def _age(a: Int) = a + 1 def _sage(a: Int) = a - 1 def _hoge(a: Int) = a * a } implicit def BoolHoge: Hoge[Boolean] = new Hoge[Boolean] { def _age(a: Boolean) = true def _sage(a: Boolean) = false def _hoge(a: Boolean) = ! a } def age[A](a:A)(implicit ev: Hoge[A]): A = { ev._age(a) } def sage[A](a:A)(implicit ev: Hoge[A]): A = { ev._sage(a) } def hoge[A](a:A)(implicit ev: Hoge[A]): A = { ev._hoge(a) } }
そして、mainはこんな感じ。
package com.qtamaki.typeclass import Hoge.IntHoge import Hoge.BoolHoge object Main { def main(args: Array[String]): Unit = { // defined by Library author println(Hoge.age(123)) println(Hoge.sage(123)) println(Hoge.hoge(123)) println(Hoge.age(true)) println(Hoge.sage(true)) println(Hoge.hoge(true)) } }
・・・・あれ?いい感じじゃない?
スッキリ。