圏論とプログラミング読書会#2 資料
TRANSCRIPT
#2-1 前回躓いた直和集合について 直和集合(direct sum) 集合A, Bに属さない 要素 * を考える A* = (A, *) B* = (*, B) とすると 直和集合 A + B = A* ∪ B* つまり A* の各要素 と B* の 各要素すべてを 集めたものが直和集合になります
#2-1 前回躓いた直和集合について 直和集合(direct sum) 集合A, Bに属さない 要素 * を考える A* = (A, *) B* = (*, B) とすると 直和集合 A + B = A* ∪ B* つまり A* の各要素 と B* の 各要素すべてを 集めたものが直和集合になります
なぜこうするのか
#2-1 前回躓いた直和集合について 直和集合(direct sum) 集合A, Bに属さない 要素 * を考える A* = (A, *) B* = (*, B) とすると 直和集合 A + B = A* ∪ B* つまり A* の各要素 と B* の 各要素すべてを 集めたものが直和集合になります
こうすると 集合 A の元と Bの元 を 必ず分離できる
#2-1 前回躓いた直和集合について 直和集合(direct sum) 集合A, Bに属さない 要素 * を考える A* = (A, *) B* = (*, B) とすると 直和集合 A + B = A* ∪ B* つまり A* の各要素 と B* の 各要素すべてを 集めたものが直和集合になります
集合 Int と Int でも常に (Int, *) != (*, Int) となる! ここの目的は とにかく 2つの集合の共通部分をなくすこと
#2-1 前回躓いた直和集合について 直和集合(direct sum) 集合A, Bに属さない 要素 * を考える A* = (A, *) B* = (*, B) とすると 直和集合 A + B = A* ∪ B* つまり A* の各要素 と B* の 各要素すべてを 集めたものが直和集合になります
集合 Int と Int で考えると 常に (Int, *) != (*, Int) となる! こうすると その元はもともと Aに属していたか, Bに属していたか必ず分かる!
#2-1 前回躓いた直和集合について 直和集合(direct sum) 集合A, Bに属さない 要素 * を考える A* = (A, *) B* = (*, B) とすると 直和集合 A + B = A* ∪ B* つまり A* の各要素 と B* の 各要素すべてを 集めたものが直和集合になります
こうしてAに属していたという文脈をつけた集合A*と Bに属していたという文脈をつけた集合B*のすべての元を集めたものが直和集合
#2-1 前回躓いた直和集合について 直和集合(direct sum) 集合A, Bに対して A* = A × {1} B* = B × {2} とすると 直和集合 A + B = A* ∪ B* つまり A* の各要素 と B* の 各要素すべてを 集めたものが直和集合になります
だからこうしてもよい A* = { (a1, 1), (a2, 1), … } B* = { (b1, 2), (b2, 2), … } こうすると,たとえ共通部分を持っていようと,必ずA, B どちらに所属していたか 区別できますよね?
#2-1 前回躓いた直和集合について Scalaでの例:Eitherは直和型 def getIntOrStr(needToInt: Boolean): Either[Int, String] = { if(needToInt) Left(100) else Right("hoge")}Either[T,U] には Letf[T], Right[U] のどちらかのインスタンスが入る集合 Either[T,U] の要素は集合 Left T の要素すべてと,集合 Right U の要素すべて
#2-1 前回躓いた直和集合について Scalaでの例:Eitherは直和型 集合 Either[Int, Int] の要素は集合 Left[Int] の要素すべてと 集合 Right[Int] の要素すべてを集めたものLeft[Int] => (Int, *)Right[Int]=> (*, Int)直感的にIntを 左側の世界のInt,右側の世界のInt というふうに分離することはできますよね?A と B を完全に分離するような属性をそれぞれにつけた集合 A*, B* を考え,それらの要素すべてを集めたものが,A と B の直和集合だといえます.その定義のひとつとして (a:A, *) (*, b:B) といった形があるというふうに考えるとわかりやすい気がします
#2-1 前回躓いた直和集合について Scalaでの例:Eitherは直和型 Left, Right がそれぞれ Either を継承することにより直和型を実現しているfinal case class Left[+A, +B](a: A) extends Either[A, B] { def isLeft = true def isRight = false}この方法ではすでに定義されている Int と String の直和型 IntOrString を作ることが出来ない.このような場合は,型クラスというものを用いる詳細は http://nekogata.hatenablog.com/entry/2014/09/07/174814
#2-1 前回躓いた直和集合について Scalaでの例: Optionは直和型 val str1: Option<String> = Some(“hoge”)val str2: Option<String> = NoneOption[T] には Some[T] か None が入るつまり 集合 Option[T] の要素は,集合 Some[T] の全ての要素と 集合 None の全ての要素※Java8のOptionalは実装を見た感じ直和型ではないOptional<String> hoge;hoge = Optinal.of(“hoge”); // <= こいつはOptinalのインスタンスhoge = Optinal.empty(); // <= こいつもOptinalのインスタンス
#2-1 前回躓いた直和集合について Haskellでの例: Maybeは直和型 let hoge = Just 10 :: Maybe Intlet hoge = Nothing :: Maybe Intlet hoge = Just “hoge” :: Maybe Int -- => Error!let hoge = Just 1.0 :: Maybe Int -- => Error!
#2-1 前回躓いた直和集合について Swiftでの例: Optionalは直和型 Optional<T>型と ImplicitlyUnwrappedOptional<T>型 があるらしいenum Optional<T>: … { case None; case Some(T) }let foo: Int? = 1 //=> Now foo = { Some(1) }foo + 1 //=> Errorif let foo = foo { foo + 1 // => 2}
#2-2 ラムダ計算のさわり じゃあ関数とは何なのか あっ、これ、進研ゼミでならったやつだ! f(x) = x + 1 ん?ここにでてくる f ってなんやねん! おれはただ、 入力に1を足した出力を得たいだけなんや!!
#2-2 ラムダ計算のさわり じゃあ関数とは何なのか あっ、これ、進研ゼミでならったやつだ! f(x) = x + 1 ん?ここにでてくる f ってなんやねん! おれはただ、 入力に1を足した出力を得たいだけなんや!! →関数名は計算操作を考えたとき本質的でない
#2-2 ラムダ計算のさわり ラムダ抽象の導入 f(x) = x + 1 の本質は、 x という入力を受け取り x + 1 という出力を返すということ この本質だけを抽出して λx. x + 1 と書くことにする(テストに出る) こいつをλ抽象(λ-abstraction)よぶ
#2-2 ラムダ計算のさわり ラムダ抽象に慣れ親しむ ~関数適用~ f(x) = x + 1 として f(2) を書きたいときどうすりゃいいねん → λx. x + 1 (2) と書く これを「λx. x + 1 を 2 に適用する」という
#2-2 ラムダ計算のさわり ラムダ抽象に慣れ親しむ ~関数適用~ → λx. x + 1 (2) こうしたときに 仮引数 x を 2 で置き換えて 計算を進めることができる → λx. x + 1 (2) = 2 + 1 = 3 これをβ簡約(β-reduction)とよぶ
#2-2 ラムダ計算のさわり ラムダ抽象に慣れ親しむ ~カリー化~ そういえば進研ゼミで f(x, y) = x + y なんて関数も習ったぞ! たしか... 多変数関数とか呼ばれてた気がする... これどうすんねん
#2-2 ラムダ計算のさわり ラムダ抽象に慣れ親しむ ~カリー化~ f(x, y) = x + y は λx. (λy. x + y) と本質的に計算は同じ! ところで、これは x を引数にとり、 「1変数関数を返す」関数にも見える!すごい! これをカリー化(Currying)とよぶ
#2-2 ラムダ計算のさわり ラムダ抽象に慣れ親しむ ~練習問題~ (1) f(x) = 2x + 3 をλ抽象で書け
(2) f(x, y) = 3x + y をλ抽象で書け
(3) f(x, y) = 3x + y + xy をλ抽象で書け
#2-2 ラムダ計算のさわり ラムダ抽象に慣れ親しむ ~練習問題~ (1) f(x) = 2x + 3 をλ抽象で書け λx. 2x + 3
(2) f(x, y) = 3x + y をλ抽象で書け λx. λy. 3x + y (3) f(x, y) = 3x + y + xy をλ抽象で書け λx. λy. 3x + y + xy
#2-3 型なしラムダ計算 λ式の定義 ラムダ計算が扱う式をλ式と呼び,次のようにに定義する e(λ式) ::= x (変数) | λx.e (λ抽象) | e1 e2 (関数適用)
λ式は変数・λ抽象・関数適用から構成される
#2-3 型なしラムダ計算 「ラムダ計算の実行」を定義する #2-2 でやったようにラムダ計算は実行できる この実行の手順も形式的に定義する必要がありますよね? → 計算の実行を表す β-簡約(β-reduction) を導入
#2-3 型なしラムダ計算 「ラムダ計算の実行」を定義する β-簡約(β-reduction) (λx.M) N →β M[x := N]
関数適用そのものを表している (左式) は, Mの中にある束縛されている x を N に置き換えた式 にできる
#2-3 型なしラムダ計算 「ラムダ計算の実行」を定義する α-変換(α-conversion) λx.M →α λy. M[x := y]
変数の名前は本質的でなく 置き換えができることを表す変換 f(x) = x + 1 と f(y) = y + 1 は 計算の意味的にはおなじですよね?
#2-3 型なしラムダ計算 「ラムダ計算の実行」を定義する η-変換(η-conversion) λx.M x →η M
関数の外延性を表す変換 平たく言えば (左辺) に対してどんな値を与えても常に同じλ式 を返すとき,(左辺) 自体をそのラムダ式に置き換えることが出来るってこと
#2-3 型なしラムダ計算 ラムダ計算はチューリング完全 チューリングマシンについて詳しくないので 説明できる人よろしく なんかおおよそプログラミングによって実現できるようなことを、ラムダ計算でも実現できるよということらしい
#2-3 型なしラムダ計算 ラムダ計算はチューリング完全 チューリングマシンについて詳しくないので 説明できる人よろしく なんかおおよそプログラミングによって実現できるようなことを、ラムダ計算でも実現できるよということらしい → マジで!?チョーウケルwwww (おいおい、冗談もほどほどにしろよ...)
#2-3 型なしラムダ計算 ラムダ計算による if ... else ... 例えば if ... else ... の機構 true は λt,λf.t false は λt.λf.f とすると if e1 then e2 else e3 を e1e2e3 として うまい具合に if ... else ... の構造を表せる
#2-3 型なしラムダ計算 ラムダ計算による if ... else ... 例えば if ... else ... の機構 true は λt,λf.t false は λt.λf.f とすると if e1 then e2 else e3 を e1e2e3 として うまい具合に if ... else ... の構造を表せる 確認してみよう ←やってみて!!すごい!!
#2-3 型なしラムダ計算 ラムダ計算による自然数とその演算の表現 以下のように自然数をλ式で表せる. これをチャーチ数とよぶ 0 = λs.λz.z 1 = λs.λz.sz 2 = λs.λz.s(sz)
ファッ?!
#2-3 型なしラムダ計算 ラムダ計算による自然数とその演算の表現 0 = λs.λz.z 1 = λs.λz.sz 2 = λs.λz.s(sz) 足し算を plus =λg1.λg2.λs.λz.g2sg1z と定義てやればこの定義に納得できる 計算してみよう!マジすごい!
#2-3 型なしラムダ計算 ラムダ計算による自然数とその演算の表現 0 = λs.λz.z 1 = λs.λz.sz 2 = λs.λz.s(sz) 足し算を plus =λg1.λg2.λs.λz.g2sg1z と定義てやればこの定義に納得できる 計算してみよう!マジすごい! ところで自然数は 0, 1, 2 じゃだめなのか?
#2-3 型なしラムダ計算 λ式の定義(復習) ラムダ計算が扱う式をλ式と呼び,次のようにに定義する e(λ式) ::= x (変数) | λx.e (λ抽象) | e1 e2 (関数適用)
λ式は変数・λ抽象・関数適用から構成される
実はλ式ではそもそも数字すら 定義されていないので 自分で定義していく必要がある