是的,那是para
。与同构关系比较,或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
其中“ cons”分支将忽略其递归计算的参数,而只返回尾部。懒惰地评估,永远不会进行递归计算,并且在恒定时间内提取尾部。
您可以很容易地定义foldr
使用para
。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
编码版本para
,则safeTail
毕竟将花费线性时间,逐个元素复制tail元素。
就是这样: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
并尝试为二叉搜索树定义插入,首先是cata
,然后是para
。您会发现para
版本容易得多,因为您需要在每个节点上插入一个子树,而保留其他子树。
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
,方法。