13. >=> Hom 函手ことはじめ (1):Hom 集合
- 圏論では、ある対象 ● から別の対象 ● へ、複数の射が存在してもよい
- すなわち、射の集合を考えることができる ? Hom 集合と呼ぶ
- 対象 ● から対象 ● に関する Hom 集合 ? Hom(●, ●) と表す
- 下図の場合:
- Hom(●, ●) = { a, b, c, d, e }
- Hom(●, ●) = ?
13
a
e
d
b
c
14. >=> Hom 函手ことはじめ (2):舞台を集合の圏へ
- 圏 C で何か1つ対象を固定する (今回は X)
- 固定した対象と、圏 C の任意の対象に関して、Hom 集合を取る
14
X
A
D C
B
圏 C 集合の圏
Hom(X, A)
Hom(X, B)
Hom(X, C)
※Hom(X, D) は空集合
15. >=> Hom 函手ことはじめ (3):Hom 集合の間に射が存在する条件
- 対象 X から対象 A への任意の射 a
- 対象 X から対象 B へのある射 b
- これらに対して、b = f ° a を満たす射 f が存在すること
(※ b を使って X から B へ向かうことと、a と f を使って X から B へ向かうことが等しくなる,の意)
15
X
B
A
圏 C 集合の圏
Hom(X, A)
Hom(X, B)
?a
?b
?f, b = f ° a
f ° _
16. >=> Hom 函手ことはじめ (4):共変 Hom 函手
- 共変 Hom 函手とは:
- 任意の圏から集合の圏への函手であって,
- 対象 * を対象 Hom(X, *) へ移し,
- (制約を満たす) 射 f を射 f ° _ へ移す,そんな函手
16
X
B
A
圏 C 集合の圏
Hom(X, A)
Hom(X, B)
?a
?b
?f, b = f ° a
f ° _
18. >=> 共変 Hom 函手 in Scala
- 型と関数の圏をベースに、Scala 圏を考えると:
- * → Hom(X, *) なる対象の対応は、カインド X => * として表現できる
(※厳密には、指数対象というものを考えて、Hom 集合を Scala 圏に埋め込むとそうなります)
- f → f ° _ なる射の対応は、高階関数 f => f compose _ として表現できる
18
X
B
A
Scala 圏 Scala 圏
X => A
X => B
?a
?b
?f, b = f compose a
f compose _
19. >=> 実際にやってみた
// F[_] が対象の対応関係を,map が射の対応関係を表す
trait EndoFunctor[F[_]] {
def map[A, B](f: A => B): F[A] => F[B]
}
type X = Unit // 固定する型は何でも良いが、今回は Unit 型
type Xto[Y] = X => Y // Hom 函手における、対象の対応関係
implicit val homFunctor = new EndoFunctor[Xto] {
def map[A, B](f: A => B): Xto[A] => Xto[B] = // Hom 函手における、射の対応関係
f compose _
}
// 任意の函手の実装を用いて、射を相手先の圏へ移す(※といいつつ Scala 圏には閉じてるので、Endo = 自己)
def map[F[_], A, B](f: A => B)(implicit functor: EndoFunctor[F]): F[A] => F[B] =
functor.map(f)
19
20. >=> 実際につかってみた
val f: Int => String = x => s"String($x)" // Int と String を結ぶ射をピックアップ
val Fa: Xto[Int] = x => 0 // Hom(X, Int) から、要素をピックアップ
// 1. Hom(X, Int) の要素 Fa
// 2. Hom 集合を結ぶ射 map(f)
// これらから、Hom(X, String) の要素 Fb を導出
val Fb: Xto[String] = map(f).apply(Fa)
println(Fb(())) // String(0) と表示される
20
- 実装してみての感想:
- 高階カインドは、やはりあると表現力が高い ?
- implicit parameter は、auto capturing default parameter だと思った ?
- 本当は map(f).apply(Fa) の部分を、map(f)(Fa) と書けたらかっちょよい