什么是同态?


96

通过阅读这个经典论文,我卡上paramorphisms。不幸的是,该部分很薄,并且Wikipedia页面什么也没说。

我的Haskell翻译是:

para :: (a -> [a] -> b -> b) -> b -> [a] -> b
para f base = h
  where
    h []       =   base
    h (x:xs)   =   f x xs (h xs)

但是我不认为-我对类型签名或所需结果没有任何直觉。

什么是同质性,有什么有用的例子在起作用?


是的,我已经看过这些 问题,但是它们并没有直接涵盖同质性,仅指向了可能对参考有用的资源,但对学习材料却没有帮助。


1
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs),方法。
Daniel Fischer

根据此Wiki页面,同态“在归纳数据类型上建模原始递归”。这意味着什么/帮助吗?
休恩

4
我在评论这些问题之一时指出的杰里米·吉本斯(Jeremy Gibbons)的“裂变”论文是非常有用的学习材料。cs.ox.ac.uk/jeremy.gibbons/publications/fission.pdf它非常清楚地通过许多递归模式工作。
斯蒂芬·泰特利2012年

1
Daniel的重写可以简化为 para f base xs = foldr g base (init $ tails xs) where g (x:xs) = f x xs。这让我们想起了Common Lisp的maplist
Will Ness

Answers:


110

是的,那是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使用parapara从中进行定义有点棘手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)

定义parawith 的诀窍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 TreeFwhere 给出的二进制搜索树

data TreeF sub = Leaf | Node sub Integer sub

并尝试为二叉搜索树定义插入,首先是cata,然后是para。您会发现para版本容易得多,因为您需要在每个节点上插入一个子树,而保留其他子树。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.