【参考になりません】Haskellの型クラスをScalaに移植する #1
モナドについて学ぶべくすごいH本を読んだので復習として型クラスをScalaに移植してみます。
本と同じようにFunctor -> Applicative Functor -> Monadという順番で進めていこうと思います。
ちなみにScalaは趣味で少し書いたことがある程度です。
Haskellはこの本を読んだだけで、コードは一切書いたことがありません。
今回はまずFunctor型クラスと、そのListインスタンスを実装しようかと。
まずは何も見ずに自分で考えて実装した後、可能であればScala界の代表的な関数型ライブラリである(合ってます?)ScalazとCatsの実装を見て答え合わせします。
※疑問だらけでめちゃくちゃ中途半端な状態で終わります
※書いてあることは高確率で間違っています
※文中の疑問や誤りについてコメントいただけると嬉しいです
回答
コード
object Main { trait Functor[F[_]] { def fmap[A, B](a: A => B, fa: F[A]): F[B] } object Functor { implicit object ListFunctor extends Functor[List] { override def fmap[A, B](a: A => B, fa: List[A]): List[B] = fa.map(a) } } def exec()(implicit f: Functor[List]): Unit = { val r = f.fmap[Int, Int](a => a * 3, List(1, 2, 3)) print(r) } def main(args: Array[String]): Unit = { exec() } }
気になる点
def exec()(implicit f: Functor[List])
コンパイルが通らないので、ここでFunctorの型パラメータをList決め打ちにしてますが、これおかしいですよね?
何のための型クラスなんだという気がするのですが。
fmapに渡した型に合わせてインスタンスを引っ張ってきて欲しい。
でも例えばこうするとコンパイルエラーになるんですよね。
def exec[A]()(implicit f: Functor[A]): Unit = { // A[Int]が欲しいのにList[Int]になってるよ!と怒られる val r = f.fmap[Int, Int](a => a * 3, List(1, 2, 3)) print(r) }
というか、こんなことやるんだったら、わざわざimplicitパラメータでインスタンスを受け取らなくても、ListFunctor.fmap[Int, Int](a => a * 3, List(1, 2, 3))
でいいんですよね。
いや、でもこれこういうものなのか...?
「ListはFunctorだよ!」ということをScala君に教えてあげられれば解決できるような気がするんですが、何か根本的に書き方が間違っているのか?
それとも、HaskellとかOCamlみたいに推論を頑張ってくれる言語じゃないと無理とか?
疑問は尽きませんが、頑張りすぎると続かないので答えを見ます。
追記:List値をベタ書きで渡しているからインスタンスもListを要求されるとかそういうことなのか?うーむそもそも何がやりたいのかわからなくなってきた。
答え合わせ
Scalaz
Functorの定義はこうなってました。
trait Functor[F[_]] extends InvariantFunctor[F] { self => ... /** Lift `f` into `F` and apply to `F[A]`. */ def map[A, B](fa: F[A])(f: A => B): F[B] ... }
これだけ見ると私のと実質同じですね。
(継承してるInvariantFunctorが何なのか不明ですがそこまでは追いたくない...)
ああ、でも私のは引数をカリー化してないですね。忘れてました。
後は、Haskellと比べるとScalazの方の引数はFunctor値が先に来てますが、文化、スタイルの違いですかね?
-- HaskellのFunctor定義(本に載ってたやつなのでたぶん古い) class Functor f where fmap :: (a -> b) -> f a -> fb
でもラムダ式にすることを考えると関数は後の方が良い気がします。
逆にHaskellの方はこれでいいのか疑問ですが、なんかそのあたりもすごいH本に書いてあった気がするが記憶が...。
さて、Listインスタンスの方なんですが、これでしょうか。
trait ListInstances extends ListInstances0 { implicit val listInstance: Traverse[List] with MonadPlus[List] with Alt[List] with BindRec[List] with Zip[List] with Unzip[List] with Align[List] with IsEmpty[List] with Cobind[List] = new Traverse[List] with MonadPlus[List] with Alt[List] with IterableBindRec[List] with Zip[List] with Unzip[List] with Align[List] with IsEmpty[List] with Cobind[List] with IterableSubtypeFoldable[List] with Functor.OverrideWiden[List] { ... }
https://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/std/List.scala
Functor.OverrideWiden[List]
というFunctor的なものを継承(ミックスイン?)してるので合ってそうです。
(OverrideWidenが何なのかは不明以下略)
で、これを見て「ああ、やっぱりそういうパターンか」とガッカリしたんですが、ここにはmapの実装はありませんでした。
で、本当に申し訳ないのですが疲れたので今回はここまでにします...。
言い訳としては、諸事情で固定回線がなくなってしまったのでデバッグしてコードを追うことがやりづらくモゴモゴ。
Cats
まずはFunctorの定義です。
@typeclass trait Functor[F[_]] extends Invariant[F] { self => def map[A, B](fa: F[A])(f: A => B): F[B] ... }
これも私のコードおよびScalazとほとんど同じですね。
というかここは変えようがないのか。
Functorを直接ミックスインしてないので推測ですが、Listインスタンスはこれっぽいです。
trait ListInstances extends cats.kernel.instances.ListInstances { implicit val catsStdInstancesForList : Traverse[List] with Alternative[List] with Monad[List] with CoflatMap[List] with Align[List] = new Traverse[List] with Alternative[List] with Monad[List] with CoflatMap[List] with Align[List] { override def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f)
こっちはmapがありますね!
内容も私のものと同じです。
やはり問題は定義より使い方なのか...?
こっちも今回はここまでにします。
あと気になったのが、ScalazにもCatsにもインスタンスとは別にsyntaxなるパッケージがあって、ListOps
みたいな感じでインスタンスに対応する?クラスが定義してあるのですが、あれが何なのか謎。
さいごに
いかがでしたか?(棒)
今回の反省点ですが、そもそもScalazやCatsを一度も使ったことがないのにいきなりコードリーディングするのは無理がありました。
あと、Scala自体の知識も足りてなさ過ぎですね。
でも、モナドは無理そうなので本を読みましたが、本当は教科書で勉強するよりこういう過程で学んでいくのが好きなんですよね...。
次は、パッと見た感じScalazより読みやすそうなCatsについてドキュメント含めてちゃんと調べるか、いきなりライブラリ読まんでもネットにサンプルが転がっていると思うのでそれを漁る感じかなと思ってます。
参考
Functor, Applicative, Traversable, Monad について
Extensible Effects in Scala
A Gentle Introduction to Haskell: Standard Classes