(免責事項:これは私の専門分野外です。私は正しいと思います(さまざまなポイントで警告が提供されています)が、...自分で確認してください。)
カタモルフィズムは、データ型のコンストラクターを他の関数に置き換える関数と考えることができます。
(この例では、次のデータ型を使用します。
data [a] = [] | a : [a]
data BinTree a = Leaf a | Branch (BinTree a) (BinTree a)
data Nat = Zero | Succ Nat
)。
例えば:
length :: [a] -> Nat
length = catamorphism
[] -> 0
(_:) -> (1+)
(残念ながら、catamorphism {..}
Haskellでは構文が利用できません(Polaで似たようなものを見ました)。私はそれに対して準引用符を書くつもりでした。)
それで、何length [1,2,3]
ですか?
length [1,2,3]
length (1 : 2 : 3 : [])
length (1: 2: 3: [])
1+ (1+ (1+ (0 )))
3
とは言うものの、後で明らかになる理由のために、それを自明に同等であると定義する方が良いです:
length :: [a] -> Nat
length = catamorphism
[] -> Zero
(_:) -> Succ
さらにいくつかのカタモルフィズムの例を考えてみましょう。
map :: (a -> b) -> [a] -> b
map f = catamorphism
[] -> []
(a:) -> (f a :)
binTreeDepth :: Tree a -> Nat
binTreeDepth = catamorphism
Leaf _ -> 0
Branch -> \a b -> 1 + max a b
binTreeRightDepth :: Tree a -> Nat
binTreeRightDepth = catamorphism
Leaf _ -> 0
Branch -> \a b -> 1 + b
binTreeLeaves :: Tree a -> Nat
binTreeLeaves = catamorphism
Leaf _ -> 1
Branch -> (+)
double :: Nat -> Nat
double = catamorphism
Succ -> Succ . Succ
Zero -> Zero
これらの多くは、新しいカタモルフィズムを形成するためにうまく構成することができます。例えば:
double . length . map f = catamorphism
[] -> Zero
(a:) -> Succ . Succ
double . binTreeRightDepth = catamorphism
Leaf a -> Zero
Branch -> \a b -> Succ (Succ b)
double . binTreeDepth
動作しますが、ある意味ではほとんど奇跡です。
double . binTreeDepth = catamorphism
Leaf a -> Zero
Branch -> \a b -> Succ (Succ (max a b))
double
これは、分散するためにのみ機能しmax
ます...これはまったくの偶然です。(同じことが当てはまりますdouble . binTreeLeaves
。)ダブリングではうまく機能しないものに置き換えた場合max
...まあ、新しい友達を定義しましょう(他の友達とうまくいきません)。double
分散しない二項演算子には、を使用します(*)
。
binTreeProdSize :: Tree a -> Nat
binTreeProdSize = catamorphism
Leaf _ -> 0
Branch -> \a b -> 1 + a*b
2つのカタモルフィズムが構成するための十分条件を確立してみましょう。明らかに、カタモルフィズムは非常に幸福にで構成されます。これはlength
、子の結果を見ずにデータ構造を生成するためです。たとえば、の場合は、好きなものに置き換えるだけで、新しいカタモルフィズムが得られます。double
map f
length
Succ
Zero
- 最初のカタモルフィズムがその子に何が起こるかを見ずにデータ構造を生成する場合、2つのカタモルフィズムがカタモルフィズムに構成されます。
これを超えると、事態はさらに複雑になります。通常のコンストラクター引数と「再帰的引数」(%記号でマークします)を区別してみましょう。したがってLeaf a
、再帰的な引数はありませんが、再帰的な引数はBranch %a %b
あります。コンストラクターの「再帰的固定性」という用語を使用して、コンストラクターが持つ再帰的引数の数を参照してみましょう。(私はこれらの用語を両方とも作成しました!適切な用語があれば、それが何であるかわかりません!他の場所でそれらを使用することに注意してください!)
最初のカタモルフィズムが何かをゼロ再帰固定コンストラクターにマップする場合、すべてが良好です!
a | b | cata(b.a)
===============================|=========================|================
F a %b %c .. -> Z | Z -> G a b .. | True
子を新しいコンストラクターに直接マップすれば、それも良いことです。
a | b | cata(b.a)
===============================|=========================|=================
F a %b %c .. -> H %c %d .. | H %a %b -> G a b .. | True
再帰的なfixityにマップする場合、1つのコンストラクター...
a | b | cata(b.a)
===============================|=========================|=================
F a %b %c .. -> A (f %b %c..) | A %a -> B (g %a) | Implied by g
| | distributes over f
しかし、それは違いではありません。たとえば、そのg1
g2
ようなものが存在する場合g (f a b..) = f (g1 a) (g2 b) ..
、それも機能します。
ここから、ルールはもっと乱雑になると思います。