コンピュータやソフトウェアのあれこれ@道民(&元道民)
math
[レポート][PRML][math]今日はPRML復々習レーン kick-off meetingの日です
5月 19th
1年前に挫折したわけですが、一応もう一回来てみました。適当にメモします(キックオフなので打ち合わせ中心だと思いますが)。
ご挨拶 / @naoya_t さん
- 今日はエクストリームリーディングします
- 自己紹介タイム
- 今後は幹事役を分散したい
- 会場安いとことか募集
- 全員に発表してもらいたい → 発表しないと身に付かない
- グループ分けして1章〜2章をエクストリームリーディングしましょう
- 暇な人は演習問題を(答え有り)
- 休憩
1章の黙読
各自15分程度目を通す。
- 1.66の導出はベイズの定理から形式的に出てくる?
- 暗にグラフィカルモデルを考える必要がある?(以降の章で出てくる)
- ここでαとβはハイパーパラメーターで確率変数ではない(独立性の問題)
- 適宜、常識的な範囲で独立性を補わなければ出ないのではないか
機械形式的に1.66を出せる人は教えて下さい!
- 1.6の情報量は何に使うの?
- EMアルゴリズムで
- ロジスティック回帰でもちらっと
- 1.68は解析的に解ける? 最終的に求まる分散1.70と1.71はよく使う式?
- 宿題。実際はgnuplotとかが勝手にやってくれる。導出の仕方を覚えておくことはよい。
2章1節の黙読
各自10分程度目を通す。
- 共役性の定義は?
- パラメータを置き換えれば一致する、程度で
2章2節の黙読
- 機械学習などで事前分布に共役な分布を選ぶ理由は?
- 悩まない、ということは1つの手
- 自然言語処理だと頻度分布とかを選んだ方が精度が高いんじゃないか → αを非対称にすればOK
- 計算の都合だけど、絶対そうしなければいけないという理由はない
- 過学習と関係ある? → 事前分布のご利益としては。ただ、他の分布でも一緒。
- 十分等計量のありがたさは?
- 後で出てくるので割愛
- ベータ分布のμのハイパーパラメータ
- 4〜5個のサンプルで初めて、いいところを探している
- 極所解に落ち入らない?
- 過学習は気をつけているけど、ベストかは不明
- データが多くなると、パラメータチューニングで苦労するという印象はある
- β分布やディリクレ分布は事前分布以外に使い道があるのか。μを使ってるのが気持ち悪い
- 後でxを使ってるので。
- 知ってる範囲では使ってるところは見たことがない
- P.76 の有効観測数は α_k - 1 では?
- 恐らくそうでしょう
- となると、α_k が 0.1 の場合に有効観測数が負になる。これはどう解釈すべき?
2章はしっかりやりたいので、次回以降に持ち越して担当決めて腰据えてやりましょう。
来月は2.3のガウス分布から2章最後まで。3章頭もやるかも。
発表スライドであらすじ、質疑を中心で。
発表時間は5分程度でも、重要と思った箇所は長い時間でも。読んでること前提でOK。
本レーンは1枠1時間程度(今回は本レーンよりだいぶ細かく切ってる)。
来月分の割当を完了。
[math][scala]95%の信頼区間とは
4月 10th
10000本のクジにx本の当たりが入っているとする。ここから100本のクジを引いた結果を元に、当たりクジが何本入っているか区間推定したい。
非復元抽出なので厳密にいえば100本中の当たりクジの本数yは超幾何分布に従うが、抽出する数と比較して全体数が大きいので二項分布で近似して良いだろう。さらに引いたクジの本数も十分に大きいので、正規分布として近似できる。
当たりくじの割合がp = x / 10000でそこから100本クジを引くのだから、確率変数yが二項分布だと考えれば平均値は100p、分散は100p(1-p)となる。これを元に正規化した、(y - 100p) / 10√(p(1-p))は標準正規分布に従うと言える。標準正規分布であれば-1.96から1.96の間の値をとる確率が95%であるから、-1.96 < (y - 100p) / 10√(p(1-p)) < 1.96 をpについて解けば*1、pがその範囲内に入る確率は95%であるということになる。これが95%の信頼区間。
見方を変えれば、この方法で推定すると、5%の確率で外れるってことになる。本当かどうか、シミュレーションしてみる。100個を無作為抽出してそこから95%の信頼区間を求めるという試行を1000回やって、実際の確率pが信頼区間に入った回数、入らなかった回数をカウントしてみる。
import scala.math._
import scala.util.Random._
val n: Int = 10000
val x: Int = 4321
val p: Double = x.toDouble / n.toDouble
val sampleN: Int = 100
val count: Int = 1000
val population: Array[Boolean] =
(1 to n).map(n => if (n <= x) true else false).toArray
def square(x: Double) = x * x
def randomSampling[T](x: Seq[T], n: Int) = {
val shuffled: Seq[T] = shuffle(x)
shuffled.take(n)
}
def solveQuadratic(
a: Double = 1, b: Double = 0, c: Double = 0
): (Double, Double) = {
val hanbetu = sqrt(square(b) - 4 * a * c)
((- b - hanbetu) / 2.0 / a, (- b + hanbetu) / 2.0 / a)
}
def estimate (samples: Seq[Boolean]): (Double, Double) = {
val n = samples.length
val x = samples count {x => x}
solveQuadratic(n + square(1.96), - 2 * x - square(1.96), square(x) / n)
}
val results = (1 to count).map { _ =>
val sample = randomSampling(population, sampleN)
val (min, max) = estimate(sample)
if (min <= p && p <= max) true else false
}
println(
"hit: %d, miss: %d" format (
results count {x => x}, results count {x => ! x}
)
)
結果は以下の通り、やはり5%は推定に失敗するという結果になる。
hit: 952, miss: 48
*1:二乗して二次不等式にして解く
[math][scala]超幾何分布と二項分布
4月 6th
非復元抽出とは、有限個のものから複数個を抽出する際に、抽出したものを戻さずに2回目以降の抽出を行うこと。こうすると二回目以降の試行の確率はだんだんと変化していくことになる。
復元抽出する場合は二項分布となるが、非復元抽出をすると超幾何分布となる。が、全体数に比べて試行回数が十分に小さい場合は、非復元抽出を二項分布で近似できる。
試しに、白玉100個、赤玉900個から玉を取り出す試行を10回行う場合に白玉が二個以下である確率を求めてみると、二項分布でも超幾何分布でも同じ値になることが分かる。
---*snip*--- def hypergeometricDistribution( n: Int, N: BigInt, Np: BigInt ): (Int) => Double = { import scala.math.pow def distribution(x: Int): Double = (combi(Np, x) * combi(N - Np, n - x)).toDouble / combi(N, n).toDouble distribution _ } val n = 10 val N = 1000 val p = 0.1 val binomial = binomialDistribution(n, p) val hypergeometric = hypergeometricDistribution(n, N, (N.toDouble * p).toInt) println("hypergeometric: %.2f" format ((0 to 2).map {hypergeometric(_)}.sum)) println("binomial: %.2f" format ((0 to 2).map {binomial(_) }.sum))
hypergeometric: 0.93 binomial: 0.93
[math][scala]二項分布とポアソン分布
4月 4th
二項分布は試行回数が多くなると階乗周りの計算量が多くなる。一回の事象の発生確率pが十分に小さければ、ポアソン分布で近似ができる。
例えば、5枚の硬貨を投げてすべて表がでるかどうかを64回やったときの分布を二項分布とポアソン分布で比較すると、以下のように高い精度で近似できていることがわかる。
import scala.math.BigInt def factor(n: BigInt): BigInt = if (n <= 1) 1 else n * factor(n - 1) def combi(n: BigInt, m: BigInt): BigInt = factor(n) / factor(m) / factor(n - m) def binomialDistribution(n: Int, p: Double): (Int) => Double = { import scala.math.pow def distribution(x: Int) = combi(n, x).toDouble * pow(p, x) * pow(1 - p, n - x) distribution _ } def poissonDistribution(m: Double): (Int) => Double = { import scala.math.pow def distribution(x: Int) = pow(m, x).toDouble / pow(2.71828, m).toDouble / factor(x).toDouble distribution _ } val n = 64 val p = 1.0 / 32.0 val binomial = binomialDistribution(n, p) val poisson = poissonDistribution(n * p) (0 to 10).foreach {n => println("[n=%d]".format(n)) println("binomial: %.3f".format(binomial(n))) println("poisson: %.3f".format(poisson(n))) }
[n=0] binomial: 0.131 poisson: 0.135 [n=1] binomial: 0.271 poisson: 0.271 [n=2] binomial: 0.275 poisson: 0.271 [n=3] binomial: 0.183 poisson: 0.180 [n=4] binomial: 0.090 poisson: 0.090 [n=5] binomial: 0.035 poisson: 0.036 [n=6] binomial: 0.011 poisson: 0.012 [n=7] binomial: 0.003 poisson: 0.003 [n=8] binomial: 0.001 poisson: 0.001 [n=9] binomial: 0.000 poisson: 0.000 [n=10] binomial: 0.000 poisson: 0.000
[math][scala]3囚人問題
3月 31st
こちらはモンティ・ホール問題より有名だと思う。
ある監獄にA、B、Cという3人の囚人がいて、それぞれ独房に入れられている。罪状はいずれも似たりよったりで、近々3人まとめて処刑される予定になっている。ところが恩赦が出て3人のうち1人だけ助かることになったという。誰が恩赦になるかは明かされておらず、それぞれの囚人が「私は助かるのか?」と聞いても看守は答えない。
囚人Aは一計を案じ、看守に向かってこう頼んだ。「私以外の2人のうち少なくとも1人は死刑になるはずだ。その者の名前が知りたい。私のことじゃないんだから教えてくれてもよいだろう?」すると看守は「Bは死刑になる」と教えてくれた。それを聞いた囚人Aは「これで助かる確率が1/3から1/2に上がった」とひそかに喜んだ。果たして囚人Aが喜んだのは正しいか?
まず「Bが死刑になる確率」と「Bが死刑になることを知る確率」が別の物だと気がつくことが大事。通常は「Cが恩赦のとき、Bが死刑になることを知る確率」と「Aが恩赦のとき、Bが死刑になることを知る確率」は同じと考えるべき。しかし、この問題文のような質問の仕方をしてしまうと、「Cが恩赦のとき、Bが死刑になることを知る確率」は1であり、「Aが恩赦のとき、Bが死刑になることを知る確率」は1/2であって、ズレが起こってしまう。
この説明でも怪しいと思う人は、やっぱりシミュレーションして試すとよい。
import scala.util.Random._ val A = 0 val B = 1 val C = 2 def playRole(keeperDecision: Array[Boolean] => Boolean): (Boolean, Boolean) = { val whoAlives = nextInt(3) val isDeath: Array[Boolean] = (0 to 2).map((x: Int) => x != whoAlives).toArray val hearBsDeath: Boolean = keeperDecision(isDeath) (hearBsDeath, isDeath(A)) } def whichIsDeath(isDeath: Array[Boolean]) = { if (! isDeath(C)) true else if (! isDeath(B)) false else nextBoolean() } val leakRate: Double = 0.8 def withNoAssumption(isDeath: Array[Boolean]) = if (isDeath(B) && nextDouble() < leakRate) true else false def statistics(keeperDecision: Array[Boolean] => Boolean): Double = { val results = (1 to 10000).map(_ => playRole(keeperDecision)).toList val resultsKnowOfB = results.filter(_._1) val numOfDead = resultsKnowOfB.count(_._2) numOfDead.toDouble / resultsKnowOfB.length.toDouble * 100 } println("normal situation: %.2f %%" format statistics(withNoAssumption)) println("ask which is death: %.2f %%" format statistics(whichIsDeath))
結果はほぼ理論値通りで、何も仮定しないと死刑となる確率は1/2になるが、この問題の仮定の場合は死刑となる確率は2/3のまま変化しない。
normal situation: 49.51 % ask which is death: 66.85 %
[math][scala]モンティ・ホール問題
3月 31st
3囚人問題は有名だけど、こちらを見かけたのは恥ずかしながら初めて。
「プレイヤーの前に3つのドアがあって、1つのドアの後ろには景品の新車が、2つのドアの後ろにはヤギ(はずれを意味する)がいる。プレイヤーは新車のドアを当てると新車がもらえる。プレイヤーが1つのドアを選択した後、モンティが残りのドアの内ヤギがいるドアを開けてヤギを見せる。
ここでプレイヤーは最初に選んだドアを、残っている開けられていないドアに変更しても良いと言われる。プレイヤーはドアを変更すべきだろうか?」
答えは、変更した方が勝率が高くなる。ベイズの定理できちんと考えれば答えは出るのだけど、非常に嘘っぽく感じてしまうのがこの問題の特徴*1。嘘っぽいなら実際やらせて確かめてみればいい。
import scala.util.Random._
def didWinGame(isChange: Boolean): Boolean = {
val doors: Array[Boolean] = Array(false, false, false)
doors(nextInt(3)) = true
val playerChoice = nextInt(3)
def tryChoosing(): Int = {
val choice = nextInt(3)
if (choice != playerChoice && ! doors(choice))
choice
else
tryChoosing()
}
val montyChoice = tryChoosing()
val finalAnswer =
if (isChange) List(0, 1, 2).filter(
(x: Int) => x != playerChoice && x != montyChoice
)(0)
else playerChoice
return doors(finalAnswer)
}
def winningRate(doGame: () => Boolean) = {
val total = 10000
val winCount = (1 to total).map(_ => doGame()).count((x: Boolean) => x)
winCount.toDouble / total.toDouble * 100
}
println("not change: %.2f %%".format(winningRate(() => didWinGame(false))))
println("change: %.2f %%".format(winningRate(() => didWinGame(true))))
実行するとほぼ理論値となる。
not change: 33.18 % change: 66.38 %
*1:この嘘っぽさは、引用した問題文だけだと前提条件が説明不足だってのもあるけど
[perl][math]あなたの圏は大丈夫? – 手続き型言語の裏に潜む罠
12月 22nd
Monads in PerlやAllows in Perlの話では、合成compと恒等射idを以下のように定めた。
sub comp($$) { my ($g, $f) = @_; sub { $g->($f->(@_)) }; } sub id { @_ }
しかし、これは厳密には圏とならない。
合成の結合則 h . (g . f) == (h . g) . f が満たされない
合成によって関数の実行順を定義できるので、副作用があっても結合則は満たされる*1。しかし、関数の構造を Inspection するような機能を使うことで結合則は崩れる。Perlの場合、具体的にはcallerがそれに値する。
以下のfは呼出スタックを2つ戻ってその行数を返す関数だが、合成をかける順番によって入れ子になる関数の個数が違うため、差異が出てしまう。よってテストは失敗する。
use Test::More; sub f { (caller(1))[2] } *g = \ *h = \ my $hg_f = comp(comp(\&h, \&g), \&f); my $h_gf = comp(\&h, comp(\&g, \&f)); is_deeply [$hg_f->()], [$h_gf->()];
恒等射の法則 id . f == f が満たされない
これはPerlの事情。Perlにはコンテキストがあるのだが、今のcompの実装では先行する関数を必ずリストコンテキストで評価してしまうため、コンテキストの変化に追従できない。
以下ではスカラコンテキストでの挙動を比較するテストが失敗する。
use Test::More; sub f { wantarray ? 'LIST' : 'SCALAR' } my $id_f = comp(\&id, \&f); is_deeply [f()], [$id_f->()]; is f(), $id_f->();
結合則と恒等射の法則のテストを通してみる
以下のようにすればテストは通る。
package ComposedSub; use overload '&{}' => sub { my $self = shift; sub { my $wantarray = wantarray; my @result; for (@$self) { @result = $wantarray ? $_->(@result) : (scalar $_->(@result)); } $wantarray ? @result : $result[0]; }; }, fallback => 1; sub new { my ($class, @subs) = @_; bless \@subs => $class; } package main; use Scalar::Util qw/blessed/; sub id { wantarray ? @_ : $_[0] } sub comp($$) { my ($g, $f) = @_; return $g if $f == \ return $f if $g == \ my @subs; for ($f, $g) { if (blessed $_ and $_->isa('ComposedSub')) { push @subs, @$_; } else { push @subs, $_; } } ComposedSub->new(@subs); }
まず(h . g) . f と h . (g . f) が完全に同じ構造になるよう ComposedSub というクラスを導入している。これによって結合則が満たされ、括弧を略して h . g . f と書いても差し支えがなくなる。
また、合成時にid()を無視することで恒等射としての性質を与えている。
が、この実装もそんなによいものではない。例えば、この実装では\&main::id のみが恒等射であり、my $hoge = sub { wantarray ? @_ : $_[0] }; は恒等射ではない別の関数である。しかし、常識的に考えると \&main::id と $hoge はイコールとすべきだろう*2。しかしイコールだとすると、comp($f, \&main::id) ≠ comp($f, $hoge) の結果矛盾してしまってうまく収集がつかない。
そもそも、この実装でcompとidが本当に法則を満たしているのか、完全には保証できていない。まだ穴があるかもしれない。そうなるとイタチごっことなる。
モデルと現実の差異とうまく付き合う
厳密に言えば、直感的に実装しただけではPerlの関数群は圏の性質を満たさない。しかし、「副作用は考えない」「リストコンテキストでのみ評価する」など、自分の考えているモデルに併せて実装時に制約を入れれば十分圏として機能するので、大抵の場合は問題ないはずだ。
逆に言えばモデルに課せられた制約からはみ出すような実装をすると理論的な恩恵は受けられなくなってしまうので、今回の評価時のコンテキストやcallerのように法則を破ってしまうような要素や、法則を満たすために実装時に課さなければならない制約についてはある程度把握しておくべきだろう。
[レポート][math]今日は 関数型都市忘年会 の日です
12月 10th
実家に帰るついでにふらっと立ち寄ります。
ってことで、札幌に向かっています。午前中は関数型都市忘年会に出席します。途中で退席予定です。
Arrows in Perl / @hiratara
後でスライド貼っときます。スライド。
はじめての函数型プログラミング / @tadsan さん
- 関数とは f(x) = 3x + 2
- あるxに対して対応する値を返す
- このセッションでの表記は「函数」で統一
- 手続き型の例
- Cの for文 → 条件式が必要
- Pythonのfor文 → イテレータ的
- 言語によって設計思考が違う
- Pythonは 手続き型+オブジェクト指向+函数型
- Haskell は純粋関数型言語
- 静的片付けの仲間は実行速度が速い
- 函数型言語とは、λ計算の概念を論理的基盤
- 函数をお手軽に
- 再帰処理
- 函数型言語 → LISP、ML、
- 手続き型の中の函数型
- Ruby, Python, JavaScript → 反復操作、函数の定義
- JavaScript => function, Ruby => lambda
- python => def と lambda と リスト内包表記
- lambda計算について → (λx. x + 2) 3 みたいなの
- Python でlambda計算
- チャーチ数の例
- RubyやPythonで函数型の考えに触れると、函数型言語に入りやすくなる
- Q. 関数型言語で好きなものはなんですか?
- A. F# と Haskell
- Q. Pythonでmap関数を避けたのはなぜ?
- A. Pythonの作者が使いたがってない。LISP的にしたくない。
- Q. どの函数型言語でもラムダ計算がベース?
- A. はい
- Q. 圏論はどうすればわかるの?
- A. 層圏トポスと清水本の圏論の章が日本語で平易
- Q. 末尾最適化がないと再帰はきつい
- A. はい。関数型言語なら大丈夫
最近書いた、関数型言語と関連する?C++プログラムの紹介/ @h_hiro_ さん
- Ubuntu 11 だとgcc4.5が入る。普通だとgcc4.3しか入らない
- 「与えられた文字列に対して、どの単語が何番目にあるかを示す連想配列を作る」
- 関数型言語では、メモリや実行速度より処理の実現方法を気にする
- C++ではメモリ使用量は重要 → メモリを節約しつつ、関数型言語的な記法
- Boost::split → 記法は簡単になるがコピーしているから却下
- 「fundoshi」"他人のふんどしでスモウをとる"
- fundoshi::string → コピーをせずに部分文字列を表現
- 長さの取り出し、文字列比較、ポインタの取得、など
- 自分で必要な部分のみ
- Ruby に慣れるとC++ はエレガントではない
- 記法にこだわりたい
- boostは頑張ってるっぽい。勉強したい
- 記法に拘るネタ(2)
- ファイルから最小の数値を見つけたい場合、int minimumのような変数を用意する
- 一番小さいものをキープするクラスを作った
- 補則「istream_iterator を使うと、cin をイテレータに変えられる」
ここで退室しました。運営の皆さん、ありがとうございました!
[perl][perl+web][math]Directing AE with Arrows
10月 23rd
@maki_daisukeさんに教えてもらったのを読んで実装してみた。
use strict; use warnings; { package AsyncArrow; use Scalar::Util qw/weaken/; use AnyEvent; use Exporter qw/import/; use Class::Accessor::Lite new => 1, rw => ['code']; our @EXPORT = qw/repeat done/; sub repeat_request(;$) { {repeat => 1, value => shift} } sub done_request(;$) { {done => 1, value => shift} } sub arr { my ($class, $f) = @_; $class->new(code => sub { my ($v, $progress, $cont) = @_; $cont->($f->($v), $progress); }); } sub compose { my ($self, $other) = @_; return (ref $self)->new(code => sub { my ($v, $progress, $cont) = @_; $self->code->($v, $progress, sub { my ($v, $progress) = @_; $other->code->($v, $progress, $cont); }); }); } sub run { my ($self, $v, $progress) = @_; $progress ||= ProgressArrow->new_without_args; $self->code->($v, $progress, sub { my ($v, $progress) = @_; return; }); return $progress; } sub repeat { my $self = shift; my $weaken_loop; my $loop = sub { my ($v, $progress, $cont) = @_; $self->code->($v, $progress, sub { my ($v, $progress) = @_; if ($v->{repeat}) { my $canceller; my $t = AE::timer 0, 0, sub { $progress->advance($canceller); $weaken_loop->($v->{value}, $progress, $cont); }; $progress->add_canceller($canceller = sub { undef $t }); } elsif ($v->{done}) { $cont->($v->{value}, $progress); } else { die } }); }; weaken($weaken_loop = $loop); (ref $self)->new(code => $loop); } } { package ProgressArrow; use parent qw/-norequire AsyncArrow/; use Scalar::Util qw/weaken/; use Class::Accessor::Lite rw => ['cancellers', 'observers']; sub new_without_args { my $class = shift; my $self = $class->new( cancellers => [], observers => [], ); weaken(my $weaken_self = $self); $self->code(sub { my ($v, $progress, $cont) = @_; push @{$weaken_self->observers}, sub { my ($v) = @_; $cont->($v, $progress); }; }); return $self; } sub add_canceller { my $self = shift; push @{$self->cancellers}, @_; } sub advance { my ($self, $canceller) = @_; @{$self->cancellers} = grep {$canceller != $_} @{$self->cancellers}; while (my $observer = shift @{$self->observers}) { $observer->(); } } sub cancel { my ($self, $canceller) = @_; while (my $cancel = shift @{$self->cancellers}) { $cancel->(); } } } use AnyEvent; use AnyEvent::Handle; sub inputted_line() { AsyncArrow->new(code => sub { my ($v, $progress, $cont) = @_; my $canceller; my $hdl = AnyEvent::Handle->new( fh => \*STDIN, on_error => sub { my ($hdl, $fatal, $message) = @_; $hdl->destroy; $! and warn "$message(fatal=$fatal)"; $cont->(); }, on_read => sub { my ($hdl) = @_; $hdl->push_read(line => sub { my ($hdl, $line, $eol) = @_; $progress->advance($canceller); $canceller->(); $cont->($line, $progress); }); }, ); $progress->add_canceller($canceller = sub { undef $hdl }); }); } my $cv = AE::cv; my $total_length = 0; my $p = inputted_line->compose(AsyncArrow->arr(sub { if (defined $_[0]) { $total_length += length $_[0]; print "INPUT: $_[0]\n"; AsyncArrow::repeat_request; } else { print "DONE\n"; AsyncArrow::done_request; } }))->repeat ->compose(AsyncArrow->arr(sub { $cv->send })) ->run; $p->compose(AsyncArrow->arr(sub { print "total length: $total_length\n" }))->compose(AsyncArrow->arr(\&AsyncArrow::repeat_request)) ->repeat ->run; $cv->recv;
ArrowsはGeneralising Monads to Arrowsにモナドの一般化として載ってます。ざっくり言えば、高階関数の圏の直積を保存するように作った、新たな圏への関手。
で、このJSの実装なんだけど、CpsA の実装までは美しいのに、 AsyncA の実装がイマイチな気がするんですよね。特に ProgressA って、状態をバリバリに持ってるSubject的なクラスなのに、AsyncA を継承して作る利点がイマイチわからず。
[perl][yapcasia][perl+web][math]YAPCで話さなかったこと
10月 16th
今回のトークでは「斜めの矢印」とか「合成」とかそういう言葉を説明に含める必要があったので圏論のことばや記法を前半で色々紹介しましたが、泥沼詳細には踏み込み過ぎないように気をつけました。その泥沼詳細にあえて踏み込んでみたい人のために、このトークに出てきた話と圏論の言葉の対応表を与えておきます。
| スライド内の言葉 | 圏論的な性質 |
|---|---|
| Type(型) | 対象 |
| Arrow(矢印)、または、関数 | 射 |
| id | 恒等射 |
| M() | 関手の対象関数 |
| モナド = M(), unit, flat_map | Kleisli triple*1 |
| 斜めの矢印、または、Kleisli射 | Kleisli圏の射 |
| map | 関手の射関数 |
| unit | 自然変換 |
| flatten | 自然変換 |
| M()+map, unit, flatten | モナド |
さらにM(), unit, flat_mapによって高階関数の成す圏のKleisli圏が構成されるとみなすと、unitはそのKleisli圏の恒等射、flat_mapを介した2つのKleisli射の合成がそのKleisli圏での合成と言えます。
勉強する順番としては、圏(対象、射、恒等射)→関手→自然変換→モナド、が最速です。
*1:プログラミング言語のモナドは、数学ではKleisli tripleと呼ばれています。数学のモナドは、後述されている3つ組をいいます。ただし、数学のモナドと数学のKleisli tripleは同値です