slide /

型クラスペディア
(The Typeclassopedia)
John Kodumal, Atlassian
jkodumal@atlassian
 
翻訳: 大村伸吾
everpeace@gmail.com

型クラス:開世界のインターフェース

*訳注:開世界の原語は"Open-world"。

型クラスは、Javaのインターフェースに似ていますが、より柔軟です。

Scalaではtraitを使って宣言します:

trait Show[A] {
	def shows(a: A): String
}

"インターフェース"をmix-inするスタイルとは微妙に違うことに注意してください:

trait Show {
	def shows(): String
}	

型クラスに型を追加するにはこうします:

implicit val IntShow = new Show[Int] {
	def shows(a: Int) = a.toString
}

型クラスを使う

Scalaのimplicitを使います:

def shows[A](a : A)(implicit shower: Show[A]) = shower.shows(a)

もしくはコンテキストバウンドを使います:

def shows[A : Show](a : A) = implicitly[Show[A]].shows(a)

(implicitly は predef で定義されています: def implicitly[A](implicit a : A) = a)

Scalaz は典型的な型クラスをpimpしてくれているので、こうするだけで出来ます:

3.shows // スコープ中に"Show[Int]"インスタンスが必要です。さもなくば型チェックに失敗します。

開世界仮説

何が得られる?

型クラス/==サブタイプポリモーフィズム

Scalaの型クラスでできないこと(少なくとも簡単ではない):

interface Show {
	String show();
}

class Foo extends Show {
	public String show() {
		return "a foo";
	}
}

class Bar extends Show {
	public String show() {
		return "a bar";
	}
}

List[Show] showList = new ArrayList(new Foo(), new Bar(), new Foo());

for (String s : showList) {
	System.out.println(s.show);
}

型クラスペディア(The Typeclassopedia)

この型クラス階層は関数型のコードにおいて大変有用であることが証明されています:

ここではファンクタ(Functor)、Applicativeファンクタ、モナド(Monad)を扱います。

*for-comprehensionとモナドの関係も扱います。

ファンクタ(Functor)

*訳注:ファンクタは関手とも呼ばれます。

ファンクタとは、単に「写像できる何か」のことで、次の一個のメソッドで定義されます:

trait Functor[F[_]] {
	def map[A,B](fa: F[A])(f: A => B) : F[B]
}

Option型に対するのファンクタのインスタンスは次のように定義されます:

implicit val OptionFunctor = new Functor[Option] {
	def map[A, B](fa: Option[A])(f: A => B) : Option[B] = 
		fa match {
			case Some(a) => Some(f(a))
			case None => None
		}
}

Haskellでは、引数が逆になります:

class Functor f where  
    fmap :: (a -> b) -> f a -> f b	

Haskell版を見ると、f がファンクタのコンテキストに持ち上げられている(lifted)のがわかります。

ファンクタとしての関数の楽しみ

"写像できるもの" から自明な例が導かれます:

自明でない例を見てみましょう: 関数です!

implicit def FunctionFunctor[R] = new Functor[({type l[a] = R=>a})#l] {
	def map[A, B](fa: R => A)(f: A => B) : R => B = (x => f(fa(x)))
}
クイズ: すこし変わった方法でmapを定義しましたが、これが表現している単純な概念とは何でしょう?

もう一度、Haskell版のfmapを見てみましょう:

	fmap :: (a -> b) -> f a -> f b

この f ar -> a 置き換えてみてください。

	fmap :: (a -> b) -> (r -> a) -> (r -> b)

*訳注:FunctionFunctor[R]Rを定義域とする関数を表していて、ここで定義されたmapR=>aa=>bからR=>bという関数を返す、つまり関数の合成の概念を表している。

余談: ここが"ボックス"のアナロジーが壊れ始めるところです。そして、「計算のコンテキスト」という曖昧な言葉を使い始めることになるのです。

*訳注:日本では"ボックス"ではなく"コンテナ"というアナロジーが使われることが多い。

ファンクタの法則

ファンクタには、ファンクタが"正しく振る舞える"ようにするための、いくつかの制約が存在します:

これらの法則は、馬鹿げたmapの定義を回避するもので、渡された関数がどう振る舞うか予測できるようにしています。

これらの法則は、型システムでは保証できないことに注意してください。

余談: パラメトリシティ

大きな弱みは大きな力を生み出す。(With great weakness comes great power...)

Ben "Pierce" Parker

多くの力は多くの問題を生み出す。(Mo' power, mo' problems...)

Biggie Smalls
def fun[A](a : A) : A = ...

funって何?

val x : List[Int] = ...
def foo[F[_] : Functor](fs : F[Int]) = ...

foo(x).lengthって何?

Applicative ファンクタ

mapだけでも様々なことが出来ますが、足りない時もあります。

次の例を考えましょう:

def parse(s : String) : Option[Int] = ...

2つのパースされた整数をmapを使って足してみましょう:

parse("3").map((x: Int) => (y: Int) =>  x + y) // ???

これだとOption[Int => Int]という型が返ってきてしまいます。どうすればよいでしょう?

もっと強力なmapのような操作が必要です。

Applicative ファンクタ

さぁFunctorに能力を追加しましょう:

trait Applicative[F[_]] extends Functor {
	def <*>[A, B](fa: F[A])(f : F[A => B]) : F[B]
	// ... Applicativeにはまだ必要なものがありますが, 後で紹介します
}

これを使うと前の例はこんな風に出来るようになります:

(parse("3")).<*>(parse("Nope").map(x => (y : Int) => x + y))

(オェッ)

Applicative の文法

Haskellでは、もうちょっとわかりやすくなります:

(+) <$> parse("3") <*> parse("Nope") 

少し一般化してみましょう。このような純粋な関数があるとします:

f x y z

この関数を"effectful"な引数を渡して呼ぶにはこのようにします:

f <$> x <*> y <*> z

ScalazのApplicativeBuilder を使うといくらかは改善できます:

(parse("3") |@| parse("Nope"))(_ + _)

|@|

(マコーレー・カルキン演算子)

Applicativeの話題をもう少し

もしOptionでない引数があったらどうする?

	(parse("3") |@| 4 /* だめだめ、マコーレーにはカルキンじゃなきゃ */) (_ + _)

そこで、小さな変更を加えましょう:

trait Applicative[F[_]] extends Functor {
	def <*>[A, B](fa: F[A])(f : F[A => B]) : F[B]
	def point[A](a : A): F[A]
}
演習: Option型に対するApplicativeファンクタのインスタンスを定義してください

モナド(Monad)のチュートリアル

そう。罠ですよ。

モナド(Monad)

さっきと同じように、新しいタスクについて考えて、何がうまく行かないかを見てみましょう。

再びparseの例を考えます:

def parse(s : String) : Option[Int] = ...

入力されるStringがクエリパラメータだったらどうでしょうか?

val x : Option[String] = params.get("x")

今、Option[String]型の値とString => Option[Int]という型の関数があります。

このときmapしかなかったらどうなるでしょうか。

x.map(parse) // Option[Option[Int]] ??

モナド(Monad)

さぁApplicativeに能力を追加しましょう:

trait Monad[F[_]] extends Applicative {
	def >>=[A, B](fa: F[A])(f : A => F[B]) : F[B]
}

これはバインド(bind)と発音します。

これで、先の例はこんな風に出来るようになります:

params.get("x") >>= parse // Option[Int]

もっとweb-basedな計算機らしい例にしてみましょう:

params.get("x") >>= parse >>= (x => (params.get("y") >>= parse) map (_ + x) )

(オェッ)

救いの糖衣構文

最後の例から、こんな典型的なパターンが考えられます:

monadicX >>= (x => monadicY >>= (y => monadicZ map (z => x+y+z))) 

最後のmapまでbindが入れ子になっています

少しリフォーマットしてみるとこうなります:

monadicX >>= (x => 
monadicY >>= (y => 
monadicZ map (z => 
   x+y+z
))) 

Scalaではfor-comprehensionを使ってこう書けます:

for {
	x <- monadicX
	y <- monadicY
	z <- monadicZ
} yield x + y + z

(大分良くなった!)

埋込み言語としてのモナド

モナドのアイデアを証明するのにこの武器を紹介します。これを考えてみてください:

for {
	x <- monadicX
	y <- monadicY
} yield x + y

Q: このコードはどう動くでしょうか?

A: モナドの種類によります。

モナドを使ったfor-comprehensionは、モナドによってセマンティクスを定義される埋込みプログラミング言語と見なせます:

モナド セマンティクス
Option 無名の例外
Reader グローバルな環境
Validation 記述的な例外
List 非決定的な計算