(免责声明:这超出了我的专业领域。我相信我是正确的(在不同的地方提供了警告),但是......请自行验证。)
catamorphism 可以被认为是一个函数,它将数据类型的构造函数替换为其他函数。
(在本例中,我将使用以下数据类型:
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 中看到过类似的东西)。我一直想为它写一个 quasiquoter。)
那么,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
让我们尝试建立两个变质两个组成的充分条件。显然,任何 catamorphism 都将非常高兴地由 length、double 和 map f 组成,因为它们在不查看子结果的情况下生成了它们的数据结构。例如,在length 的情况下,您可以将Succ 和Zero 替换为您想要的任何内容,然后您就拥有了新的变质。
- 如果第一个 catamorphism 生成一个数据结构而不查看其子级会发生什么,则两个 catamorphism 将组成一个 catamorphism。
除此之外,事情变得更加复杂。让我们区分普通的构造函数参数和“递归参数”(我们将用 % 符号标记)。所以Leaf a 没有递归参数,但Branch %a %b 有。让我们使用构造函数的术语“递归固定性”来指代它具有的递归参数的数量。 (这两个术语都是我编出来的!我不知道什么是合适的术语,如果有的话!小心在别处使用它们!)
如果第一个 catamorphism 将某些东西映射到零递归固定性构造函数中,那么一切都很好!
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
如果我们映射到一个递归固定的构造函数...
a | b | cata(b.a)
===============================|=========================|=================
F a %b %c .. -> A (f %b %c..) | A %a -> B (g %a) | Implied by g
| | distributes over f
但这不是iff。例如,如果存在g1 g2 这样g (f a b..) = f (g1 a) (g2 b) ..,那也可以。
我预计,从这里开始,规则会变得更加混乱。