我们可以认为这是从cata 到histo 再到dyna 的泛化连续统一体。用recursion-schemes的术语来说:
Foldable t => (Base t a -> a) -> (t -> a) -- (1)
Foldable t => (Base t (Cofree (Base t) a) -> a) -> (t -> a) -- (2)
Functor f => (f (Cofree f a) -> a) -> (t -> f t) -> (t -> a) -- (3)
其中 (1) 是 cata,(2) 是 histo,(3) 是 dyna。这种概括的高级概述是histo 通过维护所有部分“正确折叠”的历史来改进cata,而dyna 通过让对任何类型t 进行操作来改进histo,只要我们可以为它制作一个f-coalgebra,而不仅仅是Foldable 那些(它有通用的Base t-coalgebras 作为Foldable 见证数据类型是最终的coalgebras)。
我们几乎可以通过简单地查看实现它们的类型需要什么来读取它们的属性。
例如,cata 的经典用法是定义foldr
data instance Prim [a] x = Nil | Cons a x
type instance Base [a] = Prim [a]
instance Foldable [a] where
project [] = Nil
project (a:as) = Cons a as
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr cons nil = cata $ \case
Nil -> nil
Cons a b -> cons a b
重要的是,我们注意到foldr 仅使用“上一个”右折叠值来生成“下一个”部分右折叠值。这就是它可以使用cata 实现的原因:它只需要最直接的先前部分折叠结果。
正如histo 概括cata 我们应该能够对它做同样的事情。这是一个基于histo 的foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr cons nil = histo $ \case
Nil -> nil
Cons a (b :< _) -> cons a b
我们可以看到,我们不再立即获得前一个折叠结果,而是必须进入Cofree 的第一层才能找到它。但是Cofree 是一个流,可能包含无限多的“先前折叠值”,我们可以尽可能深入地挖掘它。这就是赋予histo“历史”力量的原因。例如,我们可以使用histo 编写一个相当直接的tail,这比单独使用cata 更难:
tail :: [a] -> Maybe [a]
tail = histo $ \case
Nil -> Nothing -- empty list
Cons _ (b :< x) -> case x of
Nil -> Just [] -- length 1 list
Cons a _ -> fmap (a:) b
样式有点间接,但本质上是因为我们可以回顾过去的两个步骤,我们可以对长度为 1 的列表的响应与长度为 0 的列表或长度为 -n 的列表不同.
为了将histo 推广到dyna 的最后一步,我们只需将自然投影替换为any 余代数。因此,我们可以很容易地根据dyna 实现histo
histo phi = dyna phi project -- project is from the Foldable class
所以现在我们可以将histo 折叠应用于任何甚至可以部分视为列表的类型(好吧,只要我们继续运行示例并将Prim [a] 用作Functor、f )。
(理论上,这个余数最终停止有一个限制,例如我们不能处理无限流,但这更多地与理论和优化有关,而不是使用。在使用中,这样的事情只是必须足够懒惰并且足够小才能终止。)
(这反映了通过project :: t -> Base t t 的能力来表示初始代数的想法。如果这真的是一个完全归纳类型,那么你只能在到达终点之前投射这么多次。)
要复制链接论文中的加泰罗尼亚数字实例,我们可以创建非空列表
data NEL a = Some a | More a (NEL a)
data NELf a x = Somef a | Moref a x deriving Functor
并在称为natural 的自然数上创建余代数,适当展开它会产生倒计时NEL
natural :: Int -> NELf Int Int
natural 0 = Somef 0
natural n = Moref n (n-1)
然后我们将histo 样式折叠应用于自然数的NELf 视图,以生成n-th 加泰罗尼亚数。
-- here's a quick implementation of `dyna` using `recursion-schemes`
zcata
:: (Comonad w, Functor f) =>
(a -> f a) -> (f (w (w c)) -> w b) -> (b -> c) -> a -> c
zcata z k g = g . extract . c where
c = k . fmap (duplicate . fmap g . c) . z
dyna :: Functor f => (f (Cofree f c) -> c) -> (a -> f a) -> a -> c
dyna phi z = zcata z distHisto phi
takeC :: Int -> Cofree (NELf a) a -> [a]
takeC 0 _ = []
takeC n (a :< Somef v) = [a]
takeC n (a :< Moref v as) = a : takeC (n-1) as
catalan :: Int -> Int
catalan = dyna phi natural where
phi :: NELf Int (Cofree (NELf Int) Int) -> Int
phi (Somef 0) = 1
phi (Moref n table) = sum (zipWith (*) xs (reverse xs))
where xs = takeC n table