型クラスの話
型クラス入門
http://halcat0x15a.github.io/slide/functional_scala/#/
抽象化の手法のひとつ
Semigroupを例にScalaでの型クラスをみていく
型クラスはtraitで宣言する
trait Semigroup[A] { def append(x: A, y:A): A }
結合則を満たしている
append(x, append(x,y)) == append(append(x,y),z)
- 型クラスのインスタンスはimplicitをつけて宣言する
- 型クラスのインスタンスは型を明示すべきである
Semigroupを使ったメソッドを定義 doubleメソッド 型クラスはカリー化されたimplicit パラメータにとります。
Scalazだとこんな感じの命名規則
implicitパラメータは型パラメータと同名の変数を付けることがある
implicitがスコープ内にあればdoubleメソッドは次のように呼び出すことができる 故に、IntかStringかどうかはあまり選ばない
assert(double(2) == 4) assert(double("hoge") == "hogehoge")
implicitパラメータを明示しない記法も存在する
def quadruple[A: Semigroup](a: A) = double(double(a))
これはContext Bounds(暗黙的に引数がつくので、引数を省略できる)
二項演算は演算子として定義
implicit class SemigroupOps[A](x: A)(implicit A: Semigroup[A]) { def <>(y: A): A = A.append(x, y) } assert(1 <> 2 <> 3 == 6) assert("foo" <> "bar" <> "baz" == "foobarbaz")
いろいろな方法からimplicitパラメータを検索できる
Monoidを例にimplicitの検索についてみてみる
trait Monoid[A] extends Semigroup[A] { def zero: A }
メソッド定義(畳み込みでappendするメソッド アスタリスクで可変長引数を表現)
def append[A](xs: A*)(implicit A: Monoid[A]): A = xs.foldLeft(A.zero)(A.append)
ListをMonoidで抽象化してみる
implictはdefで宣言することで型パラメータをもつことが可能である
object Semigroup { implicit def listMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] { def zero: List[A] = Nil def append(x: List[A], y: List[A]): List[A] = x ::: y } }
こんな感じで使えます
assert(double(List(0, 1, 2)) == List(0, 1, 2, 0, 1, 2)) assert(append(List(0), List(1), List(2)) == List(0, 1, 2))
- SemigroupとMonoidのどちらに対してもインスタンスを提供できる
- 型クラス→スーパークラスのコンパニオンオブジェクト
- MonoidはSemigroupに依存している 依存関係がよくわからない
- 型クラスのコンパニオンオブジェクトとスーパークラスのコンパニオンオブジェクトだと検索順序があるのでは
- インスタンスを定義しているときにいくつか検索するポイントがある
- データ型のコンパニオンオブジェクト
- 型クラススーパークラスのコンパニオンオブジェクト
- 型クラスのコンパニオンオブジェクト
NEWTYPE
ひとつの型に関して複数のインスタンスが定義されることがある
implicit val sumMonoid: Monoid[Int] = new Monoid[Int] { def zero: Int = 0 def append(x: Int, y: Int): Int = x + y } implicit val productMonoid: Monoid[Int] = new Monoid[Int] { def zero: Int = 1 def append(x: Int, y: Int): Int = x * y }
importによる明示的なインスタンスの選択
値クラスを使ってインスタンスが一意にしてもよい
case class Sum(value: Int) extends AnyVal case class Product(value: Int) extends AnyVal implicit val sumMonoid: Monoid[Sum] = new Monoid[Sum] { def zero: Sum = Sum(0) def append(x: Sum, y: Sum): Sum = Sum(x.value + y.value) } implicit val productMonoid: Monoid[Product] = new Monoid[Product] { def zero: Product = Product(1) def append(x: Product, y: Product): Product = Product(x.value * y.value) }
IntのMonoidを数値に一般化
implicit def sumMonoid[A](implicit A: scala.math.Numeric[A]): Monoid[A] = new Monoid[A] { def zero: A = A.zero def append(x: A, y: A): A = A.plus(x, y) } implicit def productMonoid[A](implicit A: scala.math.Numeric[A]): Monoid[A] = new Monoid[A] { def zero: A = A.one def append(x: A, y: A): A = A.times(x, y) }
implictは階層構造をつくることで優先順位をつけることができる
trait LowPrioritySemigroupImplicits { implicit def numeric[A](implicit A: scala.math.Numeric[A]): Monoid[A] = new Monoid[A] { def zero: A = A.zero def append(x: A, y: A): A = A.plus(x, y) } } object Semigroup extends LowPrioritySemigroupImplicits { implicit def int: Monoid[Int] = new Monoid[Int] { def zero: Int = 0 def append(x: Int, y: Int): Int = x + y } }
higher-kinded type
- 型パラメータをとる型パラメータを指定できる
- 例: Functor
mapを使った例
import scala.language.higherKinds trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] }
Functorは写像とよばれる
- mapのように関数をカリー化されたパラメータにとることで型推論を効かせることができる
- 型パラメータ[_]
implicit val listFunctor: Functor[List] = new Functor[List] { def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f) } implicit val optionFunctor: Functor[Option] = new Functor[Option] { def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f) }
Functorを使った実装例
def pair[F[_], A](fa: F[A])(implicit F: Functor[F]): F[(A, A)] = F.map(fa)(a => (a, a))
assert(pair(List(0, 1, 2)) == List((0, 0), (1, 1), (2, 2))) assert(pair(Option(0)) == Some((0, 0)))
- EitherもFunctorとみなせる
- Eitherは型パラメータを2つ取る
implicit def eitherFunctor[A]: Functor[({ type F[B] = Either[A, B] })#F] = new Functor[({ type F[B] = Either[A, B] })#F] { def map[B, C](fa: Either[A, B])(f: B => C): Either[A, C] = fa.right.map(f) }
- 型の部分適用の記法
- type F[B]
- Either[A, B]
- type F[B] = Either[A, B]
この例だと型を指定する必要がある(型を指定しないとコンパイルエラー)
type StringEither[A] = Either[String, A] assert(pair(Right(0): StringEither[Int]) == Right((0, 0)))
まとめ
- 型クラスを使いこなす
- implicitを知る
- Scalaの型推論を知る
俺的まとめ
- Functor Monoid
- implicit
- コンパニオンオブジェクト

Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド (impress top gear)
- 作者: Paul Chiusano,Rúnar Bjarnason,株式会社クイープ
- 出版社/メーカー: インプレス
- 発売日: 2015/03/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (6件) を見る