是的,那就是 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
的两个参数为输入数据的每个递归子对象计算一个递归值(即列表的尾部),而 para
的参数既包括原始子对象,也包括从它递归计算得出的值。
一个很好地表达了使用 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
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)
foldr
定义para
的技巧是重构原始数据的副本,这样我们就可以在每一步中访问尾部的副本,即使我们没有访问原始数据。最后,snd
丢弃输入的副本并只给出输出值。这不是很高效,但如果您对纯表现力感兴趣,para
与foldr
相比并没有更多的优势。如果您使用这个foldr
编码版本的para
,那么safeTail
最终将花费线性时间,逐个复制尾元素。para
是foldr
的更方便版本,它让您立即访问列表的尾部以及从中计算出的值。data Fix f = In (f (Fix f))
you have
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)
再次强调,两者是相互定义的,使用相同的“复制”技巧从cata中定义para
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
再次强调,para
并没有比cata
更具表现力,但如果需要轻松访问输入的子结构,则更为方便。
编辑:我想起了另一个好例子。
考虑由Fix TreeF
给出的二叉搜索树,其中
data TreeF sub = Leaf | Node sub Integer sub
尝试定义二叉搜索树的插入,首先作为 cata ,然后作为 para 。您会发现 para 版本更容易,因为在每个节点上,您需要在一个子树中插入,但保留另一个子树不变。
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
。 - Daniel Fischerpara f base xs = foldr g base (init $ tails xs) where g (x:xs) = f x xs
。这让我想起了Common Lisp的maplist
。 - Will Ness