昨日の「zipが自然変換だという話」において、zipの自然性は次の可換図式で表現できると述べました。
PLL(A, B) - zip_(A,B) → LP(A, B) | | PLL(f, g) LP(f, g) ↓ ↓ PLL(C, D) - zip_(C,D) → LP(C, D)
ここで、PLLとLPは、C×C→C という関手です。A, B, C, D は圏Cのすべての対象に渡って動くことになります。また、f:A→C、g:B→D もあらゆる射に対して変化させます。全称記号を使った論理式で書くなら:
- ∀A, B, C, D∈|C|.∀f∈C(A, C), g∈C(B, D). PLL(f, g);zipC,D = zipA,B;LP(f, g)
です。写像の等式 h = k は、∀z∈Z. h(z) = k(z) の形なので、それも展開すると:
- ∀A, B, C, D∈|C|.∀f∈C(A, C), g∈C(B, D).∀z∈PLL(A, B). zipC,D(PLL(f, g)(z)) = LP(f, g)(zipA,B(z))
エライこと全称記号が続いた論理式になります。「すべての…のすべての…のすべての…の」といった意味なので、1個や2個の事例について確認しても何の意味もありません。とはいえ、1個か2個の事例でも確認してみたいものです。
CatyScriptは明示的な型付けを書くのが割と得意なので、完全に型付けしたzipを書いて、それが自然変換であることを zip-is-natural という述語で表現します。
/** テストサンプル */ command test-sample :: void -> Logical { [ [true, false, false, true], [4, 5, 6, 7] ] | zip-is-natural"to-string" "num:inc" };
この事例では、4つの対象A, B, C, Dには次のように型(集合)が代入されます。
- A = boolean, B = integer, C = string, D = number
f:A→C は to-string : boolean→string、g:B→D としては num:inc : integer→number(数に1を足す)を使います。実際に試すデータは、PLL(boolean, string) の要素である [ [true, false, false, true], [4, 5, 6, 7] ] です。
caty:test> test-sample @True [ [ [ "true", 5 ], [ "false", 6 ], [ "false", 7 ], [ "true", 8 ] ], [ [ "true", 5 ], [ "false", 6 ], [ "false", 7 ], [ "true", 8 ] ] ]
自然変換としてのzipの定義はこの下にあります。
/** 射を引数にするためのナンチャッテ定義 */ type Code<X, Y> = string(remark="X -> Y のコードを表す文字列"); /** リスト関手 */ type List<A default any> = [A*]; class List = { command fmap<A default any, B default any> [Code<A, B> code] :: List<A> -> List<B> { each {[pass, %1] | eval} }; }; /* ペアリング関手 */ type Pair<A default any, B default any> = [A, B]; class Pair = { command fmap<A default any, B default any, C default any, D default any> [Code<A, C> code1, Code<B, D> code2] :: Pair<A, B> ->Pair<C, D> { =[ [pass, %1] | eval, [pass, %2] | eval, ] }; }; /* PLL = Pair(List(-), List(-)) 関手 */ type PLL<A default any, B default any> = Pair<List<A>, List<B>>; class PLL = { command fmap<A default any, B default any, C default any, D default any> [Code<A, C> code1, Code<B, D> code2] :: PLL<A, B> ->PLL<C, D> { =[ each {[pass, %1] | eval}, each {[pass, %2] | eval}, ] }; }; /* LP = List(Pair(-, -)) 関手 */ type LP<A default any, B default any> = List<Pair<A, B>>; class LP = { command fmap<A default any, B default any, C default any, D default any> [Code<A, C> code1, Code<B, D> code2] :: LP<A, B> ->LP<C, D> { each { =[ [pass, %1] | eval, [pass, %2] | eval, ] } }; }; /** 自然変換としてのzip */ command zip<A default any, B default any> :: PLL<A, B> -> LP<A, B> { list:zip }; /** zipの自然性 */ command zip-is-natural<A default any, B default any, C default any, D default any> [Code<A, C> code1, Code<B, D> code2] :: PLL<A, B> -> Logical { [ PLL.fmap<A, B, C, D> %1 %2 | zip<C, D> , zip<A, B> | LP.fmap<A, B, C, D> %1 %2 , ] | eq }; /** テストサンプル */ command test-sample :: void -> Logical { [ [true, false, false, true], [4, 5, 6, 7] ] | zip-is-natural<boolean, integer, string, number> "to-string" "num:inc" };