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

セカイノカタチ

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

マーブルワーズ

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

Scala Haskell

前回の続き。

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

既存の型クラスに新たなインスタンスを追加する

前回の記事で、最終的に僕の中でimplicit parameterがスッキリとした形で記述出来たので大変満足している。| ´ - ω - ` |

しかし、Haskellの型クラスの特徴である、「後からinstanceを追加できる」と言う要件を満たしているか検証し忘れたので、検証してみた。

Hoge.scalaに対して、別のファイルでMyHoge.scalaを定義する。

package com.qtamaki.typeclass

object MyHoge {
  implicit def myHoge: Hoge[String] = {
    new Hoge[String] {
      def _age(a: String) = a + "!!"
      def _sage(a: String) = a + "..."
      def _hoge(a: String) = a + a
    }
  }
}

main側に追記

package com.qtamaki.typeclass

import Hoge.intHoge
import Hoge.boolHoge
import MyHoge.myHoge

object Main {
  def main(args: Array[String]): Unit = {
    // defined by Library author 
    println(Hoge.age(123))
    println(Hoge.age(true))
    
    // MyHoge
    println(Hoge.age("Hello"))
  }
}

これで、普通に新しいHogeインスタンスが追加できた。
intHogeとboolHogeの例だけでは、「オーバーロードとどう違うの???」と言う気持ちになるが、これなら胸をはってドヤ顔出来ると言うもの。
implicit parameterを使えば、後からライブラリーの利用者視点で次々とインスタンスを追加していくことができる。
様々な型に特化した、xxxHogeを追加しても、呼び出しはHoge.{age|sage|hoge}と一貫している。メソッドに渡す型によって暗黙的に適切な実装が選ばれるというわけだ。素敵!抱いて!

Functorの定義

勢いに乗って、Functorも定義しちゃおう。

package com.qtamaki.typeclass

trait Functor[F[_]] {
  def _fmap[A,B](r: F[A], f: A => B): F[B]
}
object Functor {
  implicit def optionFunctor: Functor[Option] = {
    new Functor[Option] {
      def _fmap[A,B](r: Option[A], f: (A => B)): Option[B] = r match {
        case Some(a) => Some(f(a))
        case None => None
      }
    }
  }
  implicit def listFunctor: Functor[List] = {
    new Functor[List] {
      def _fmap[A,B](r: List[A], f: (A => B)): List[B] = r match {
        case Nil => Nil
        case x::xs => f(x) :: _fmap(xs, f) 
      }
    }
  }
  def fmap[A,B,F[_]](r: F[A])(f: A => B)(implicit ev: Functor[F]): F[B] = {
    ev._fmap(r, f)
  }
}

快調快調♪
Functorも、traitのメソッドは利用者には必要ないため_fmapと内部的な名前にしている(本当は利用者から隠蔽したいけど無理っぽい。| ´ - ω - ` |)。

これで、ListとOptionに対してFunctorを定義できた。あとは、Functor.fmap呼び出して使うだけだ。

package com.qtamaki.typeclass

import Functor.optionFunctor

object Main {
  def main(args: Array[String]): Unit = {
    val x = Functor.fmap(List(1,2,3))({x => x + 1})
    println(x.mkString(","))
    val y = Functor.fmap(Some(1))(_ + 1) // コンパイルエラー!!!
    println(y)
  }
}

あれ?|0_0|)???

List版は呼べるけど、Option版は呼べないぞ?

まーでも、Listは動いてるし、とりあえずこれは宿題っっ。今日はこれまで。(おい