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

セカイノカタチ

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

マーブルワーズ

implicit parameterを用いたFunctor型クラスをOptionで使う

さて、前回まででimplicit parameterを使ってHaskellの型クラス的なものを実装する方法は理解できた。

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

しかし、最後にハットリクンのトホホ落ちよろしく、コンパイルエラーが出て解説を終了してしまったダメな私。| ´ - ω - ` |

今回は、その辺のところをスッキリさせたい。

Functorの中で何が起きたのか?

問題解決には、原因究明が基本。ソースコードの全体は前回の記事を見てほしい。ここでは抜粋のみ貼る。

// 呼ぶ側
Functor.fmap(Some(1))(_ + 1)
// 呼ばれる側
def fmap[A,B,F[_]](r: F[A])(f: A => B)(implicit ev: Functor[F]): F[B]

まず、implicitと言うか、型パラメータの解釈を、見てみたい。呼ぶ側は、Functor.fmapをSome(1)で呼んでいる。つまり、Some[Int]が渡される。

def fmap[A,B,F[_]](r: Some[Int])(f: A => B)(implicit ev: Functor[F]): F[B]
                      ^^^^^^^^^

すると、なしくずし的にfmapの型が推論される。

def fmap[Int,Int,Some[Int]](r: Some[Int])(f: A => B)(implicit ev: Functor[Some[Int]]): F[Int]
                                                                  ^^^^^^^^^^^^^^^^^

ここで、evの型が、Functor[Option[Int] ]であれば、optionFunctorを選択できるが、Functor[Some[Int] ]だと選択できるimplicit defがない。
これが、コンパイルエラーの原因だった。

解決方法

解決方法を、3日ぐらい考えた。| ´ - ω - ` |

まず、簡単に思いつくのはsomeFunctorを定義すること。

  implicit def someFunctor: Functor[Some] = {
    new Functor[Some] {
      def _fmap[A,B](r: Some[A], f: (A => B)): Some[B] = Some(f(r.get)) 
    }
  }

しかし、コレジャナイ感が半端ない。代数的データ型が台無しだ。これじゃないよなー。

次に、呼出側でキャスト。

    val y = Functor.fmap(Some(1).asInstanceOf[Option[Int]])(_ + 1)

美しくない・・・・・。

やってることは一緒だけど、メソッドをかます。

  def mayBePlus1(o: Option[Int]): Option[Int] = Functor.fmap(o)(_ + 1)
  println(mayBePlus1(Some(1)))

何とか、妥協できるレベルか。実践では、Some(1)なんて渡すこと無いだろうし、何らかの形でメソッド経由になるだろうから、これで問題ないはず。多分。

結論

取りあえず、Functor[Option]もモノになったと思う。
fmapの定義側で、共変やら下限境界やらを使って何とかしようとした時期もありましたが、うまくいきませんでした。| ´ - ω - ` |
このブログを読んでくださった奇特な方で、良い解決方法をご存知の方がありましたらコメントください。(__)

最後に、『「古典的なオブジェクト指向的設定のもと」で「同じような」効果を生む』と言うオダスキー先生の言葉か、リフレインしたのでありました。まる。