6. Haskellのデータ型と関数は圏になる
対象=データ型 単位元律
Int, Bool, [Int], Maybe id . f = f = f . Id
Int,
結合律
射=関数 (f . g) . h = f . (g . h)
not :: Bool→Bool,
concat :: [[a]] → [a],
恒等射:
id 圏論の考え方を
射の合成=関数合成 適用できる、が
f.g
13. リストの構造に関する再帰
例: パターン
sum :: [Int] → Int 空リストの場合の値
sum [] = 0 consの場合の値を、
headの値と、tailに対して
sum (x:xs) =
関数を適用した結果から
x + sum xs 計算
コードで書くと:
length :: [a] → Int f :: [X] → Y
length [] = 1 f [] = n
length (x:xs) = f (x:xs) = c x (f xs)
1 + length xs
14. 高階関数foldrによるパターンの抽象化
パターン: foldrを使った定義例:
f [] = n sum = foldr (+) 0
f (x:xs) = c x (f xs) length = foldr
(λ_ n → n+1) 0
高階関数として抽象化! xs ++ ys =
foldr :: (a→b→b) → b foldr (:) ys xs
→ [a] → b map f = foldr
foldr c n [] = n (λx xs → f x : xs) []
foldr c n (x:xs) =
c x (foldr c n xs)
18. 圏論でのリスト型の定義
型Xと関数 n :: ()→X, c :: (A, X)→X の組で、
各 Y, n’ :: ()→Y, c :: (A,Y)→Y に対して
h . n = n’ と c’ . h = h . c を満たす
h :: X→Y が唯一つ存在
唯一つ存在するもの
唯一つ存在
n c 圏論では等式を
() X (A, X) 図式で表現:
h h 図式が可換
n’ ⇔
二点間の任意の経
Y (A, Y) 路にそって合成した
c’ 結果が等しい
19. Haskellのリスト型との対応
X = [A] h . n = n’ ⇔
n ≒ [] foldr n’ c’ [] = n’
c ≒ (:) h . c = c’ . h ⇔
foldr n’ c’ (x:xs) = c’ x (foldr n’ c’ xs)
h ≒ foldr n’ c’
n c
() X (A, X) 面倒なので、
以降では暗黙に
h h (非)カリー化し、
n’ また X と ()→X
を同一視する
Y (A, Y)
c’
21. 他の帰納的データ型の場合の例 (自然数)
自然数のデータ型 iterを使った定義例:
data Nat = Zero plus :: Nat→Nat→Nat
| Succ Nat plus n = iter n Succ
畳み込み用の関数 mult :: Nat→Nat→Nat
mult n =
iter :: a→(a→a)
iter Zero (plus n)
→ (Nat→a)
iter z s Zero = z
iter z s (Succ n) =
s (iter z s n)
(iter z s n は z に s を
n回適用する)
22. 自然数の特徴付け
任意の型X と z :: X, s :: X→X に対して、
下図を可換にする関数 h : Nat→X が
唯一つ存在。 (これが iter z s)
Zero Succ
() Nat Nat
h h
z
X X
s
25. The Evolution of a Haskell Programmer
http://www.willamette.edu/~fruehr/haskell/
evolution.html
階乗を色々な書き方で書いてみる話
Categorical Programming に関係するもの
Beginning graduate Haskell programmer
Origamist Haskell programmer
Cartesianally-inclined Haskell programmer
Ph.D. Haskell programmer
Post-doc Haskell programmer
以降の説明は分からなくても気にしないで
26. Beginning graduate Haskell programmer
(graduate education tends to liberate one from petty concerns about,
e.g., the efficiency of hardware-based integers)
-- Nat, plus, mult の定義 -- two versions of
factorial
-- primitive recursion fac' :: Nat → Nat
primrec :: a → (Nat → a fac' = primrec one (mult .
→ a) → Nat → a Succ)
primrec z s Zero = z
primrec z s (Succ n) = (zero : one : two :
s n (primrec z s n) three : four : five : _) =
iterate Succ Zero
iterの強化版:
sの引数にnが追加
27. 原始帰納法
定義
primrec z s Zero = z
primrec z s (Succ n) = s n (primrec z s n)
fac' = primrec one (mult . Succ)
これって本当に階乗になってる?
fac’ Zero = primrec one (mult . Succ) Zero = one
fac’ (Succ n)
= primrec one (mult . Succ) (Succ n)
= (mult . Succ) n (primrec one (mult . Succ) n)
= (mult . Succ) n (fac’ n) = mult (Succ n) (fac’ n)
28. 原始帰納法 (cont’d)
自然数の普遍性の別の形:
任意の型 X と z :: X と s :: Nat→X→X に
対して、下図を可換にする h :: Nat→X が
ただ一つ存在。(それが primrec z s)
Zero Succ
() Nat Nat
h id &&& h
z
X (Nat, X)
s
29. 原始帰納法 (cont’d)
primrec
primrec :: a → (Nat → a → a) → Nat → a
primrec z s Zero =z
primrec z s (Succ n) = s n (primrec z s n)
実はiterで表現できる
primrec’ z s = snd .
iter (Zero, z) (λ(a,b) → (Succ a, s a b))
練習問題: 等しさを証明してみよう
30. Origamist Haskell programmer
(always starts out with the “basic Bird fold”)
-- (curried, list) fold and an application -- hylomorphisms, as-is or "unfolded"
fold c n [] = n (ouch! sorry ...)
fold c n (x:xs) = c x (fold c n xs)
refold c n p f g =
prod = fold (*) 1 fold c n . unfold p f g
= foldr
refold' c n p f g x =
-- (curried, boolean-based, list) unfold and if p x
an application then n
unfold p f g x = else c (f x) (refold' c n p f g (g x))
if p x ≒unfoldr
then []
else f x : unfold p f g (g x) -- several versions of factorial, all
(extensionally) equivalent
downfrom = unfold (==0) id pred fac = prod . downfrom
fac' = refold (*) 1 (==0) id pred
fac'' = refold' (*) 1 (==0) id pred
31. 標準関数で書き直すと
prod :: [Int] → Int -- hylomorphism (unfolded)
prod = foldr (*) 1 refold' c n f x =
case f x of
downfrom :: Int → [Int] Nothing → n
downfrom = unfoldr d Just (a,x) → c a (refold' c n f x)
d :: Int → Maybe (Int, Int) -- several versions of factorial, all
d 0 = Nothing (extensionally) equivalent
d x = Just (x,x-1) fac :: Int → Int
fac = prod . downfrom
-- hylomorphisms (as-is) fac' = refold (*) 1 d
refold :: (a → b → b) → b fac'' = refold' (*) 1 d
→ (c → Maybe (a, c))
→ (c → b)
refold c n f = foldr c n . unfoldr f
32. 無限リストを生成する関数 unfoldr
unfoldr downfrom 3 = unfoldr d 3
3
:: (b → Maybe (a, b))
↓d
→ (b → [a]) Just (3, 2)
unfoldr f x = ↓d
Just (2, 1)
case f x of ↓d
Nothing → [] Just (1, 0)
Just (a, y) → ↓d
Nothing
a : unfoldr f y
⇒ 3 : 2 : 1 : []
33. 無限リストの型の圏論的な定義
任意の型 X, 関数 f :: X → Maybe (a, X) に
対して、以下を可換にする関数 h : X→[a] が
唯一つ存在。(それが unfoldr f )
out
[a] Maybe (a,[a])
-- リストの分解関数
out :: [a]→Maybe (a,[a])
h fmap (id *** h) out [] = Nothing
out (x:xs) = Just (x, xs)
X Maybe (a,X)
f
34. Hylomorphism (refold)
Haskellでは
有限リストの型 = 無限リストの型
なので、
unfoldr f :: x→ [a]
foldr c n :: [a]→y unfoldrで広げて
foldrで畳みなおす
が結合できる!
refold c n f = foldr c n . unfoldr f :: x → y
35. Hylomorphismを使った階乗の定義
階乗の定義
fac = refold (*) 1 d = foldr (*) 1 . unfoldr d
計算例
unfoldr d foldr (*) 1
4 [4,3,2,1] 24
中間データが生成されて、効率が悪いが、
refold’ を使えば中間データ無しに計算可能
36. Cartesianally-inclined Haskell programmer
(prefers Greek food, avoids the spicy Indian stuff;
inspired by Lex Augusteijn’s “Sorting Morphisms” [3])
Origamist と同じく Hylomorphism で定義
ただし、関数名がギリシャ語風に
foldr → cata (Catamorphism)
unfoldr → ana (Anamorphism)
refold → hylo (Hylomorphism)
引数の与え方も少し変わっている
37. Ph.D. Haskell programmer
(ate so many bananas that his eyes bugged out, now he needs new
lenses!)
またもや Hylomorphism を使った定義だけど
型レベルでの不動点演算子を使って型定義
newtype Mu f = In (f (Mu f))
data N x = Zero | Succ x
type Nat = Mu N
再帰を行う関数も汎用的なものに
foldr や iter が
cata :: Functor f ⇒ (f a → a) → (Mu f → a) に。
unfoldr が
ana :: Functor f ⇒ (a → f a) → (a → Mu f ) に。
38. Post-doc Haskell programmer
(from Uustalu, Vene and Pardo’s “Recursion Schemes from
Comonads” [4])
Beginning graduate Haskell programmer
と同じく、原始帰納法で階乗を定義
ただし、Ph.D. Haskell programmer での
汎用的な定義を利用
また、原始帰納法を直接書くのではなくて、
Comonadic Iteration という超一般的な
再帰パターンの特別な場合として記述
41. プログラム例
-- 終対象 (ユニット型) -- 自然数型
right object 1 with ! left object nat with pr is
end object; zero: 1 → nat
succ: nat → nat
-- 直積 (タプル) end object;
right object prod(a,b) with pair is
pi1: prod → a -- 関数定義の例
pi2: prod → b let add = ev.pair(pr(curry(pi2),
end object; curry(s.ev)).pi1, pi2)
let mult = ev.prod(pr(curry(zero.!),
curry(add.pair(ev, pi2))), id)
-- べき対象 (関数型)
let fact = pi1.pr(pair(s.zero, zero),
right object exp(a,b) with curry is pair(mult.pair(s.pi2, pi1), s.pi2))
ev: prod(exp,a) → b
end object;
42. データ型定義
Left Object / Right Object の二種類
Left Object
普通の有限のデータ型
自然数, リスト, 論理値, 直和, etc.
値の構造が重要
Right Object
無限かもしれないデータ型
ユニット型, 直積, 関数, 無限リスト, オートマトン, etc,
値の振る舞いが重要
Haskellと違って有限のリストと無限リストは別の型
43. 終対象 (ユニット型)
right object 1 with !
end object;
1 任意の対象Xが与えられたときに、
!
Xから1への関数が一意に存在
その関数を ! と表記
X
44. 自然数型
left object nat with pr is X, z :1→X, s : X→X に対
zero: 1 → nat して、下図を可換にする関
succ: nat → nat 数 pr(z,s): nat→X が一意
end object; に存在
zero succ
1 nat nat
pr(z,s) pr(z,s)
z
X X
s
45. 自然数型
zero succ
1 nat nat
pr(z,s) pr(z,s)
z
X X
s
pr(z,s) . zero = z
pr(z,s) . succ = s . pr(z,s) Haskellで書いた
iterがprに対応
が成り立つ。
これを左辺⇒右辺の書き換え規則とみなす。
47. 直積 (タプル型)
right object prod(a,b) with pair is
pi1: prod → a 定義しようとしている
pi2: prod → b 型の引数は
end object; 書かない
pi1 pi2 任意のX, f:X→a,
a prod(a,b) b g:X→b に対して、
左図を可換にする
pair(f,g) pair(f,g) :
f g X→prod(a,b)
X が一意に存在
48. 直積 (タプル型)
pi1 pi2
a prod(a,b) b
pair(f,g)
f g
X
pi1 . pair(f, g) ⇒ f
pi2 . pair(f, g) ⇒ g
prod(f,g) ⇒ pair(f.pi1, g.pi2)
49. べき対象 (関数型)
(関数型ですら組み込み型ではない)
right object exp(a,b) with curry is
ev: prod(exp, a) → b
end object; evはevalの略だけど、
Lispでのapplyに相当
ev
prod(a,b) b ev . prod(curry(f), id)
⇒f
prod(curry(f), id) exp(f, g)
f
⇒
prod(X,b) curry(g . ev . prod(id, f))