Categorical Programming(3)

CPL/CHARITYでは、CoListとunfoldが定義できるはずなので、無限リストを生成するとかすると、停止しない計算が書けてしまうんじゃなかろうかという気がする

http://d.hatena.ne.jp/m-a-o/20091101#p2
から続き。


その1。CPLの原論文でCartesian Closed Categoryがnatural number objectを持つとき、足し算は、
add = apply . (iter(curry(pi2) , curry(succ . apply)) × id_Nat)
というように書けるとある。


微妙に記号を変えたけど、iterはNatに対するfold関数。Iはterminal object(直積の単位対象)。(.)は射の合成。全部型を書くと
iter : Hom(I,X) × Hom(X,X) -> Hom(Nat , X)
pi2 : Hom(A × B , B)
succ : Hom(Nat , Nat)
curry : Hom(A × B , C) -> Hom(A , B => C)
apply : Hom((B => A) × B , A)
id_Nat : Nat -> Nat
ついでに、
zero:Hom(I , X)
で、iter(zero , succ) = id_Nat
iterの型は、Hom(I+X,X)->Hom(Nat,X)の方がいいけど、あんま細かく書くとめんどい


Haskellだと、Homと->と=>は区別されず、敢えて明示的にcurry化まで書くと

import Prelude hiding (curry)

data Nat = Zero | Succ Nat deriving Show

apply(f,x) = f x
iter(z , f) Zero = z()
iter(z, f) (Succ n) = iter((\_ -> f $ z ()), f) $ n
pi2(x,y) = y
curry f = (\x -> \y -> f(x,y))
prod(f,g) = (\(x,y)->(f x , g y))

add = apply . prod(iter(curry(pi2) , curry(Succ . apply)) , id)


上の定義を見ても、ぱっと見て足し算だとは(少なくともわたしには)分からないのだけど、Haskellであれば

add(n,m) = iter(\_->n , Succ) m

と書くのが自然で分かりやすい。けど一般の圏ではXとI=>Xは同型になるけど、何の仮定もなしにHomとinternal homを関係付けることができないので、(\_->n)に相当するものを作ることができないので、上のような定義になっているんだと思う

Charityでは

def add: nat * nat -> nat
       = (m,   n)  => {| zero: () => n
                       | succ: x  => succ x
                       |}
                          m.

で済むらしい

あと、上のpi2の型は、Hom(I × Nat , Nat)なので、一般のsymmetric monoidal closed categoryでも足し算くらいはできる。foldとunfoldがあるので、symmetric monoidal closed categoryでもcartesian closed categoryと「同じ計算能力がある」かもしれない


その2。高階原始帰納関数の正確な定義はよく分からないけど、Ackermann関数は、頑張るとiterを使って書ける。

ack 0 n = n + 1
ack m 0 = ack (m-1) 1
ack m n = ack(m-1) $ ack m (n-1)

で、これは

ack Zero = Succ
ack (Succ m) = iter(\_->(ack m (Succ Zero)) , ack m)

で、もう一段頑張ると

ack = iter(\_->Succ , \f -> iter(\_ -> (f $ Succ Zero) , f))

パズルの世界。不安なので、普通にHaskellで書いておくと

iter x f 0 = x
iter x f (n+1) = iter (f x) f n
ack = iter (+1) (\f-> iter (f 1) f)