免责声明:这超出了我的专业范围。我相信我的翻译是正确的(在不同的点提供了警告),但请自己验证。
一个“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+)
很遗憾,在Haskell中没有可用的catamorphism {..}
语法(我在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
让我们来看一些更多的例子:
关于"catamorphisms":
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
让我们来尝试建立两个折叠的足够条件以进行组合。显然,任何一个折叠都可以和
length
、
double
和
map f
进行组合,因为它们会在不查看子结果的情况下产生它们的数据结构。例如,在
length
的情况下,您只需将
Succ
和
Zero
替换为您想要的内容即可获得新的折叠。
- 如果第一个折叠在不查看其子级结果的情况下生成数据结构,则两个折叠将组成一个折叠。
除此之外,事情变得更加复杂。让我们区分常规构造函数参数和“递归参数”(我们将使用%符号标记)。因此,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
如果我们将一个构造函数映射到递归的固定性中...
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) ..
,那么也可以使用。
从这里开始,规则会变得更加混乱,我预计会出现这种情况。
h :: F1 A -> A
吗? - Sebastien