是的,就是这样para
。与 catamorphism 比较,或foldr
:
para :: (a -> [a] -> b -> b) -> b -> [a] -> b
foldr :: (a -> b -> b) -> b -> [a] -> b
para c n (x : xs) = c x xs (para c n xs)
foldr c n (x : xs) = c x (foldr c n xs)
para c n [] = n
foldr c n [] = n
有些人将超态称为“原始递归”,而变态 ( foldr
) 称为“迭代”。
其中foldr
's 的两个参数为输入数据的每个递归子对象(这里是列表的尾部)给出一个递归计算的值,para
's 的参数获取原始子对象和从中递归计算的值。
一个很好地表达的示例函数para
是列表的适当满足的集合。
suff :: [x] -> [[x]]
suff = para (\ x xs suffxs -> xs : suffxs) []
以便
suff "suffix" = ["uffix", "ffix", "fix", "ix", "x", ""]
可能更简单的仍然是
safeTail :: [x] -> Maybe [x]
safeTail = para (\ _ xs _ -> Just xs) Nothing
其中“cons”分支忽略其递归计算的参数,只返回尾部。懒惰地评估,递归计算永远不会发生,并且在恒定时间内提取尾部。
您可以很容易地定义foldr
using ;定义frompara
有点棘手,但它肯定是可能的,每个人都应该知道它是如何完成的!para
foldr
foldr c n = para (\ x xs t -> c x t) n
para c n = snd . foldr (\ x (xs, t) -> (x : xs, c x xs t)) ([], n)
para
定义with的技巧foldr
是重建原始数据的副本,以便我们在每一步都可以访问尾部的副本,即使我们无法访问原始数据。最后,snd
丢弃输入的副本,只给出输出值。它不是很有效,但如果你对纯粹的表现力感兴趣,para
给你的不超过foldr
. 如果你使用这个foldr
-encoded 版本para
,那么safeTail
毕竟将花费线性时间,逐个元素地复制尾部元素。
所以,就是这样:这para
是一个更方便的版本,foldr
它使您可以立即访问列表的尾部以及从中计算出的值。
在一般情况下,使用作为仿函数的递归固定点生成的数据类型
data Fix f = In (f (Fix f))
你有
cata :: Functor f => (f t -> t) -> Fix f -> t
para :: Functor f => (f (Fix f, t) -> t) -> Fix f -> t
cata phi (In ff) = phi (fmap (cata phi) ff)
para psi (In ff) = psi (fmap keepCopy ff) where
keepCopy x = (x, para psi x)
再一次,这两者是相互定义的,para
由cata
相同的“复制”技巧定义
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
同样,para
并不比 更具表现力cata
,但如果您需要轻松访问输入的子结构,则更方便。
编辑:我记得另一个很好的例子。
考虑由Fix TreeF
where给出的二叉搜索树
data TreeF sub = Leaf | Node sub Integer sub
并尝试为二叉搜索树定义插入,首先是 a cata
,然后是 a para
。您会发现该para
版本要容易得多,因为您需要在每个节点处插入一个子树,但保留另一个子树。