読者です 読者をやめる 読者になる 読者になる

セカイノカタチ

世界のカタチを探求するブログ。関数型言語に興味があり、HaskellやScalaを勉強中。最近はカメラの話題も多め

マーブルワーズ

Haskellの型クラスとScalaのimplicit parameterの対応について

haskell scala

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が出てきていまして、HaskellScalaが比較されているので非常にわかりやすいです。
だた、このスライドの場合、利用例が無いので、最後のところでイメージがぼんやりしてしまいました。
(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とした方が良いということになるよね。

改良版Hoge.scala

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))
  }
}

・・・・あれ?いい感じじゃない?

スッキリ。