最后我终于明白了。请看我演讲的视频和幻灯片:
原始问题:
在我努力理解通用递归模式(即使用Fix
的递归模式)的过程中,我发现编写各种模式的列表版本是很有用的,这使得理解实际的模式变得容易得多(没有Fix
的额外开销)。
但是,我还没有弄清楚如何定义zygo
和futu
的仅列表版本。
以下是我迄今为止的专业定义:
cataL :: (a -> b -> b) -> b -> [a] -> b
cataL f b (a : as) = f a (cataL f b as)
cataL _ b [] = b
paraL :: (a -> [a] -> b -> b) -> b -> [a] -> b
paraL f b (a : as) = f a as (paraL f b as)
paraL _ b [] = b
-- TODO: histo
-- DONE: zygo (see below)
anaL :: (b -> (a, b)) -> b -> [a]
anaL f b = let (a, b') = f b in a : anaL f b'
anaL' :: (b -> Maybe (a, b)) -> b -> [a]
anaL' f b = case f b of
Just (a, b') -> a : anaL' f b'
Nothing -> []
apoL :: ([b] -> Maybe (a, Either [b] [a])) -> [b] -> [a]
apoL f b = case f b of
Nothing -> []
Just (x, Left c) -> x : apoL f c
Just (x, Right e) -> x : e
-- DONE: futu (see below)
hyloL :: (a -> c -> c) -> c -> (b -> Maybe (a, b)) -> b -> c
hyloL f z g = cataL f z . anaL' g
hyloL' :: (a -> c -> c) -> c -> (c -> Maybe (a, c)) -> c
hyloL' f z g = case g z of
Nothing -> z
Just (x,z') -> f x (hyloL' f z' g)
如何定义列表的histo
、zygo
和futu
?
3条答案
按热度按时间w1e3prcc1#
Zygomorphism是我们给由两个半相互递归函数构成的折叠所取的一个夸张的数学名称。
想象一个函数
pm :: [Int] -> Int
(表示 plus-minus),它交替地将+
和-
穿插在一串数字中,使得pm [v,w,x,y,z] = v - (w + (x - (y + z)))
,你可以用原始递归写出它:很明显,
pm0
不是compositional--你需要检查整个列表在每个位置的长度,以确定你是在加还是在减。Paramorphism 为这种类型的原始递归建模,当折叠函数在折叠的每次迭代中需要遍历整个子树时。因此,我们至少可以重写代码,以符合既定的模式。但是这是低效的。
lengthEven
在paramorphism的每次迭代中遍历整个列表,导致O(n2)算法。我们可以通过注意到
lengthEven
和para
都可以表示为foldr
的 * 退化 * 来取得进展.........这意味着我们可以将这两个操作合并到一次遍历列表中。
我们有一个折叠依赖于另一个折叠的结果,我们能够将它们融合到列表的一次遍历中,接合形态正好捕捉到了这种模式。
在折叠的每一次迭代中,
f
看到了上一次迭代的答案,就像在退化中一样,但是g
看到了两个函数的答案。g
将自己与f
纠缠在一起。我们将
pm
写为左右对称,使用第一个折叠函数来计算列表的长度是奇数还是偶数,使用第二个函数来计算总数。这是经典的函数式编程风格。我们有一个高阶函数来完成消耗列表的繁重工作;我们所要做的就是插入逻辑来聚合结果。构造显然是终止的(您只需要证明
foldr
的终止性),而且它比原始的手工编写的版本更高效。旁白:@AlexR在评论中指出,左右对称有一个大姐姐,叫做 mutumorphism,它捕捉了所有它的荣耀的相互递归。
mutu
推广zygo
,因为 * 两个 * 折叠函数都被允许检查另一个在前一次迭代中的结果。只需忽略额外的参数,即可从
mutu
恢复zygo
。zygoL f = mutuL (\x p q -> f x p)
当然,所有这些折叠模式都是从列表推广到任意函子的不动点:
比较一下
zygo
和zygoL
的定义。还要注意,zygo Fix = para
和后三个折叠都可以用cata
来实现。在折叠学中,每件事都与其他事相关。您可以从通用版本恢复列表版本。
dfuffjeb2#
思路是这样的:当我们使用输入列表时,折叠代数会输出一个
b
s序列。histo
会记下出现的每个b
,并将其附加到结果表中。历史中的项数等于您处理的列表层数--当您拆下整个列表时,操作历史记录的长度将与列表的长度相等。下面是列表(ory)的迭代历史:
History
是一个由事件和结果组成的列表,在末尾有一个额外的结果,对应于[]
-事件。一旦你从右到左折叠了整个列表,你的最终结果将在堆栈的顶部。
(It碰巧
History a
是一个comonad,但是headH
(néeextract
)是我们定义histoL
所需要的全部。)History
用其相应的结果标记输入列表的每一层。cofree comonad 捕获标记任意结构的每一层的模式。(我通过将
ListF
插入Cofree
并进行简化,得出了History
。)把它与自由单子相比较,
Free
是联产品类型;Cofree
是产品类型。Free
将f
的千层面分层,在千层面的底部具有值a
。Cofree
将千层面分层,在每一层具有值a
。自由单子是广义的外部标记树;共生自由共生单胞菌是广义的内部标记树。有了
Cofree
,我们就可以从列表推广到任意函子的不动点,并且再次恢复列表版本。
旁白:
histo
是futu
的对偶。请看它们的类型。futu
是histo
,箭头翻转,Free
替换为Cofree
。组织形态见过去;未来形态可以预测未来。就像cata f . ana g
可以融合成一个 hylomorphism 一样,histo f . futu g
也可以融合成一个chronomorphism。即使你跳过了数学部分,this paper by Hinze and Wu也提供了一个很好的、示例驱动的关于组织形态及其用法的教程。
hgc7kmma3#
由于还没有其他人回答
futu
,我将尝试用ListF a b = Base [a] = ConsF a b | NilF
来完成。取
recursion-schemes
中的类型:futu :: Unfoldable t => (a -> Base t (Free (Base t) a)) -> a -> t
.我将忽略
Unfoldable
约束,用中的[b]
替换t
。Free (ListF b) a)
是一个列表,末尾可能有一个a
类型的洞。这意味着它同构于([b], Maybe a)
。所以现在我们有:去掉最后一个
ListF
,注意到ListF a b
同构于Maybe (a, b)
:现在,我非常确定玩类型俄罗斯方块会导致唯一合理的实现:
总结所得到的函数,
futuL
取种子值和可以产生 * 至少一个 * 结果的函数,并且如果产生了结果,则可能取新的种子值。一开始我以为这相当于
实际上,也许或多或少是这样的,但一个显著的区别是,真实的的
futu
保证了生产率(即,如果f
总是返回,您将永远不会陷入等待下一个列表元素的困境)。