前回の記事にコメントをいただいて、Functor とか Monad なんかの高階型はどうするのか改めて考えてみました。
結論から言うと Scala は関数リテラルで型パラメータとれないので、関数リテラルだけでなんとかしようというのは不可能っぽいです(いまんところの私の頭では)。
しかし、implicity 使ったり context-bound を使わなくてもなんとかなりそうです。ここまで来ると型クラスじゃねーだろっ。と言われるかもしれませんがアドホック多相にはなってると思います。
ということで、Functor をやってみます。Haskell だとこんな感じですね。
class Functor f where fmap :: (a -> b) -> f a -> f b |
Scala だと
import language.higherKinds trait Functor[A] { type F[_] def fmap[B](func: A => B): F[B] } trait FunctorFunctions { def fmap[A, B](func: A => B)(f: Functor[A]): m.F[B] = f.fmap(func) } |
Option 型に適用してみます。
trait FunctorInstance { implicit class OptionFunctor[A](option: Option[A]) extends Functor[A] { type F[_] = Option[_] def fmap[B](func: A => B): Option[B] = option.map(func) } } |
使い方。
val allInstances = new FunctorInstance with FunctorFunctions {} import allInstances._ val maybeInt = Option(3) val func: Int => Int = _ * 2 println(maybeInt.fmap(func)) println(fmap(func)(maybeInt)) // こんな風にも使えます def doubleInt(f: Functor[Int]): f.F[Int] = f.fmap(func) println(doubleInt(maybeInt)) |
def は撲滅できませんでしたが、context-bound が撲滅できるのが私個人としては嬉しいです。それにやっぱりコード量はこっちのほうが少ないし!
でも、この fmap、カリー化を用いた部分適用(という言い方でいい?)できない! ぎゃふん!
この定義だと問題点は色々あると思いますけど、たとえばScalazでこういう定義
https://github.com/scalaz/scalaz/blob/v7.0.4/core/src/main/scala/scalaz/Traverse.scala#L81
def sequence[G[_]:Applicative,A](fga: F[G[A]])
で、「任意のApplicativeを引数の制約として要求する」とか「任意のMonadを(ry」というのがありますけど、そういうのが、この型クラスの定義では自然に書けなくなります。
Scalazだったら Functor[List] という形でオブジェクトを引き回せますけど
trait Functor[A] {
type F[_]
def fmap[B](func: A => B): F[B]
}
において、F[_]の型だけ固定したくても、同時にAの型も固定しないといけなくなるので。
(それを避ける、補う仕組みをすごく頑張れば入れられるかもしれないけど、そしたらコード増えて意味がなくなる)
コメントありがとうございます!
確かに仰るとおりですね。