SlideShare a Scribd company logo
Introduction to
Categorical Programming
                 (revised)
               HAMA.jp 2009
            酒井 政裕 @ヒビルテ
今日話したいこと

なぜ圏論か?
Haskell での圏論プログラミング
The Evolution of a Haskell Programmer
圏論プログラミング言語 CPL
圏論とは?

圏論(けんろん、category theory)は、数学的
構造とその間の関係を抽象的に扱う数学理
論の 1 つである。考えている種類の「構造」を
持った対象とその構造を反映するような対象
間の射の集まりからなる圏が基本的な考察
の対象になる。
             Wikipedia 「圏論」 より
圏

                                  対象(Object)
idX                         idY   X, Y, Z,
                f                 射(Morphism)
      X             Y
                                  f, g, idX,
                                  射の合成: ∘
                        g
          g∘f
                                  結合律
                    Z             (h∘g)∘f = h∘(g∘f)
                        idZ       単位元
                                  f ∘ idX = f = idY ∘ f
圏論

 対象と射(矢印)による抽象化
 等式を図式で表現          具体例色々

              対象    射

              集合    関数
              位相    連続関数
              空間
              群     準同型
              型     プログラム
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
抽象的無意味

圏論は

 general abstract
    nonsense
と呼ぶ人もいるくらい、一般的
           一般的かつ抽象的
           一般的  抽象的
よくある質問:
 そんなのが、プログラミングに何の関係が?
 いったい何の役に立つのか?
なぜ圏論なのか?

ソフトウェアにとって「抽象化」は死活的に
重要
 対象ドメインの概念を計算機上でどう表現する?
 どうレイヤやモジュールを分け、どういう構造でプロ
 グラムを書くのか?
圏論は数学で育まれた抽象化の技法の宝庫
 ソフトウェアやプログラミングに使える概念も沢山
 特に関数型言語は数学や圏論と相性が良い
 Haskellを使ってて良かったね (^^
ここで CM です

Software Abstractions
  Daniel Jackson
  MIT Press / April 2006


抽象化とソフトウェアの
設計に悩むあなたに。
  注: 圏論とは特に関係あ
  りません。
今日話したいこと

なぜ圏論か?
Haskell での圏論プログラミング
The Evolution of a Haskell Programmer
圏論プログラミング言語 CPL
Haskell での圏論プログラミング

  圏論プログラミング
      =
 圏論の概念を使って、
  プログラムを構造化
Haskell での圏論プログラミング

 Haskellで圏論というとモナドとかArrowとか?

 今回はそういう話ではなくて、
 再帰的なプログラムの書き方についての話

 まずは圏論のことは一度忘れて、普通の
 Haskellプログラムを
リストの構造に関する再帰
例:                    パターン
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
高階関数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)
なぜfoldrのような形で関数を書くのか?

良い性質をもった再帰だから
 人間が読む上で、処理の流れが分かりやすい
 c と n が停止する式で、xsが有限リストなら、
 foldr c n xs も停止する。
 プログラムの性質が推論しやすい
   例) foldr c n . map f = foldr (λx a → c (f x) a) n
  結果として、最適化しやすい
それに対して、無制限の再帰は
  命令型言語での goto のようなもので、やっかい
♥
明示的な再帰を使って書く前に、foldrとかで
書けないか、考えて見ましょう。
foldrと圏論の関係は?

リスト型とは

    nil, cons, foldr
が定義された抽象データ型
foldrは単なる便利な高階関数ではなく、
実はリスト型にとって根源的な関数
⇒ 圏論的なリスト型の定義に由来
圏論でのリスト型の定義

     型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’            結果が等しい
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’
普遍性

「○○で、各○○に対して、△△を満たす関
数がただ一つ存在する」
この性質は「普遍性」と呼ばれる
圏論では普遍性でデータ型を特徴付ける

普遍性を満たせば、実装は何でも良い
 たとえば チャーチエンコーディングとか
 http://homepages.inf.ed.ac.uk/wadler/papers/fre
 e-rectypes/free-rectypes.txt
他の帰納的データ型の場合の例 (自然数)

自然数のデータ型                 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回適用する)
自然数の特徴付け
 任意の型X と z :: X, s :: X→X に対して、
 下図を可換にする関数 h : Nat→X が
 唯一つ存在。 (これが iter z s)

        Zero         Succ
   ()          Nat          Nat

               h            h
        z

               X             X
                      s
この先の話
このような話はリストや自然数に限らず、他の帰納
的データ型でも実は同様
有限リストを消費するfoldrの双対、
無限リストを生成するunfoldr
より複雑な関数を表現する方法
  Hylomorphism, Paramorphism, Histomorphism,
  Comonadic Iteration,
⇒ とても全部は無理だけど、一部を
 The Evolution of a Haskell Programmer
 をネタに紹介
目次

なぜに圏論?
Haskell での圏論プログラミング
The Evolution of a Haskell Programmer
圏論プログラミング言語 CPL
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
 以降の説明は分からなくても気にしないで
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が追加
原始帰納法

定義
 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)
原始帰納法 (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
原始帰納法 (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))
   練習問題: 等しさを証明してみよう
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
標準関数で書き直すと

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
無限リストを生成する関数 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 : []
無限リストの型の圏論的な定義

 任意の型 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
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
Hylomorphismを使った階乗の定義

階乗の定義
  fac = refold (*) 1 d = foldr (*) 1 . unfoldr d
計算例
        unfoldr d               foldr (*) 1
   4                [4,3,2,1]                 24

中間データが生成されて、効率が悪いが、
refold’ を使えば中間データ無しに計算可能
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)
  引数の与え方も少し変わっている
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 ) に。
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 という超一般的な
 再帰パターンの特別な場合として記述
目次

なぜに圏論?
Haskell での圏論プログラミング
The Evolution of a Haskell Programmer
圏論プログラミング言語 CPL
圏論プログラミング言語 CPL

CPL (Categorical Programming Language)
圏論プログラミングの源流
特徴
  圏論に基づいたデータ型定義
  型定義から導出される項書き換え規則による実行
  ポイントフリー
  停止性の保証
プログラム例
-- 終対象 (ユニット型)                        -- 自然数型
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;
データ型定義
Left Object / Right Object の二種類
Left Object
  普通の有限のデータ型
  自然数, リスト, 論理値, 直和, etc.
  値の構造が重要
Right Object
  無限かもしれないデータ型
  ユニット型, 直積, 関数, 無限リスト, オートマトン, etc,
  値の振る舞いが重要
Haskellと違って有限のリストと無限リストは別の型
終対象 (ユニット型)

 right object 1 with !
 end object;


        1     任意の対象Xが与えられたときに、

    !
              Xから1への関数が一意に存在
              その関数を ! と表記
        X
自然数型
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
自然数型
            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に対応
が成り立つ。
これを左辺⇒右辺の書き換え規則とみなす。
自然数を用いた計算例
2倍する関数                                            pr(f,g).succ
 double = pr(zero, succ . succ) : nat→nat         ⇒ g.pr(f,g)
                                                     を適用
計算例:
 double . succ . succ . succ . zero
 ⇒ pr(zero, succ.succ).succ.succ.zero
 ⇒ succ.succ.pr(zero, succ.succ).succ.succ.zero
 ⇒ succ.succ.succ.succ.pr(zero,succ.succ).succ.zero
 ⇒ succ.succ.succ.succ.succ.succ.pr(zero,succ.succ).
    zero
 ⇒ succ.succ.succ.succ.succ.succ.zero
                                            pr(f,g).zero ⇒ f
                                                 を適用
直積 (タプル型)
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                  が一意に存在
直積 (タプル型)

               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)
べき対象 (関数型)
             (関数型ですら組み込み型ではない)
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))
簡単な関数定義

 add = ev.prod(pr(curry(pi2), curry(s.ev)),
 id)
 mult = ev.prod(pr(curry(zero.!),
 curry(add.pair(ev, pi2))), id)
 fact = pi1.pr(pair(s.zero, zero),
 pair(mult.pair(s.pi2, pi1), s.pi2))

これは流石に、変態言語(Esoteric Language)っぽいね
CPLのリソース

実装:
 http://www.tom.sfc.keio.ac.jp/~sakai/hiki/?CPL
論文:
 Categorical Programming Language
 http://www.tom.sfc.keio.ac.jp/~hagino/thesis.pdf
おわりに

普段使っている foldr や unfoldr の背景には
深遠な世界が
圏論でのデータ型の特徴づけを用いることで、
性質の良い再帰を構造的に書ける

無理に圏論プログラミングする必要はないけ
ど、変態プログラミングの一種としても楽しい
かも
参考文献
Categorical Programming Language
http://www.tom.sfc.keio.ac.jp/~hagino/thesis.pdf
Categorical programming with inductive and
coinductive types
http://www.cs.ut.ee/~varmo/papers/thesis.pdf
The Evolution of a Haskell Programmer
http://www.willamette.edu/~fruehr/haskell/evoluti
on.html

More Related Content

Introduction to Categorical Programming (Revised)