Daily Archives: 2013年10月28日

Scala 2.10 からの型クラス2(Functorはどうする?)

前回の記事にコメントをいただいて、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、カリー化を用いた部分適用(という言い方でいい?)できない! ぎゃふん!

Scala 2.10 からの型クラス

Scala で型クラス使おうという人ならば一度は目を通したことがあるだろう「空飛ぶサンドイッチのパーツ」。

Scala での型クラスの実装方法といったらたいていこの記事に書いてあるような方式になるのではないでしょうか(というか、私がググった限りではこのパターンしか見つけられなかった)。

でもこの方式、context-bound 型パラメータ使うんですよね。これのせいで def が登場しちゃうし、カリー化ができない。空飛ぶサンドイッチのパーツ自身、関数リテラル使おうぜ! と言いながらこのせいで def 使っちゃう。悔しい。それから、もっと便利にしようということで Scalaz みたいに Ops とか入れちゃって、型クラス実現するのにめんどうなこと!

ということで、Scala 2.10 から入った implicit classes 使うと型クラス簡単ですぜっていうのが今回の話です。空飛ぶサンドイッチのパーツの Depth をやってみます。

まず型クラス。ジェネリクスはいりません。

trait Depth {
  val: depth: Int
}

// お好みで関数バージョンも作りましょう
trait DepthFunctions {
  val depth: Depth => Int = _.depth
}

そして型インスタンス。

trait DepthInstances {
  implicit class TreeDepth(tree: Tree) extends Depth {
    val depth: Int = tree match {
      case Empty()    => 0
      case Leaf(_)    => 1
      case Node(l, r) => 1 + math.max(l.depth, r.depth)
    }
  }
  implicit class ListDepth(list: List[Int]) extends Depth {
    val depth: Int = list match {
      case xs => xs.size
    }
  }
}

使い方。空飛ぶサンドイッチのパーツでは、def になっていた halfDepth もこれで晴れて関数リテラルに!

val allInstances = new DepthInstances 
  with DepthFunctions {}
import allInstances._
val halfDepth: Depth => Int = _.depth / 2
// ↓ 関数バージョンを使えばこんな書き方もできます。
// val halfDepth: Depth => Int = d => depth(d) / 2
halfDepth(List(1, 2, 3, 4))

簡単! やった!