slide /
*訳注:開世界の原語は"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]"インスタンスが必要です。さもなくば型チェックに失敗します。
何が得られる?
Int は Show について知らないのです。
def unaryShows(x : Int) : String = {
implicit val AltIntShow = new Show[Int] {
def shows(i : Int) = (1 to i) map(_ => "|") mkString
}
shows(x)
}
println shows(5) // prints '5'
println unaryShows(5) // prints '|||||'
/==サブタイプポリモーフィズム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);
}
この型クラス階層は関数型のコードにおいて大変有用であることが証明されています:
ここではファンクタ(Functor)、Applicativeファンクタ、モナド(Monad)を扱います。
*for-comprehensionとモナドの関係も扱います。
*訳注:ファンクタは関手とも呼ばれます。
ファンクタとは、単に「写像できる何か」のことで、次の一個のメソッドで定義されます:
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)))
}
もう一度、Haskell版のfmapを見てみましょう:
fmap :: (a -> b) -> f a -> f b
この f a を r -> a 置き換えてみてください。
fmap :: (a -> b) -> (r -> a) -> (r -> b)
*訳注:FunctionFunctor[R]はRを定義域とする関数を表していて、ここで定義されたmapはR=>aとa=>bからR=>bという関数を返す、つまり関数の合成の概念を表している。
余談: ここが"ボックス"のアナロジーが壊れ始めるところです。そして、「計算のコンテキスト」という曖昧な言葉を使い始めることになるのです。
*訳注:日本では"ボックス"ではなく"コンテナ"というアナロジーが使われることが多い。
ファンクタには、ファンクタが"正しく振る舞える"ようにするための、いくつかの制約が存在します:
map(fa)(id) === famap(fa)(f compose g) === map(map(fa)(g))(f)これらの法則は、馬鹿げた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って何?
mapだけでも様々なことが出来ますが、足りない時もあります。
次の例を考えましょう:
def parse(s : String) : Option[Int] = ...
2つのパースされた整数をmapを使って足してみましょう:
parse("3").map((x: Int) => (y: Int) => x + y) // ???
これだとOption[Int => Int]という型が返ってきてしまいます。どうすればよいでしょう?
mapのような操作が必要です。さぁ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))
(オェッ)
Haskellでは、もうちょっとわかりやすくなります:
(+) <$> parse("3") <*> parse("Nope")
少し一般化してみましょう。このような純粋な関数があるとします:
f x y z
この関数を"effectful"な引数を渡して呼ぶにはこのようにします:
f <$> x <*> y <*> z
ScalazのApplicativeBuilder を使うといくらかは改善できます:
(parse("3") |@| parse("Nope"))(_ + _)
|@|
もし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ファンクタのインスタンスを定義してください
さっきと同じように、新しいタスクについて考えて、何がうまく行かないかを見てみましょう。
再び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]] ??
さぁ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 |
非決定的な計算 |