(免责声明:这超出了我的专业领域。我相信我是正确的(在不同的地方提供了警告),但是......请自己验证。)
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
and替换Zero
为您想要的任何内容,并且您拥有新的 catamorphism。
- 如果第一个 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) ..
,那也有效。
我预计,从这里开始,规则会变得更加混乱。