我最终搞清楚了。请参阅我给出的演讲的视频和幻灯片:
原始问题:
在我努力理解通用递归方案(即使用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
?
答案 0 :(得分:37)
Zygomorphism 是我们给两个半相互递归函数构建的折叠的高falutin'数学名称。我举个例子。
想象一个函数pm :: [Int] -> Int
(对于 plus-minus ),它将+
和-
交替穿过一个数字列表,这样{{1} }。你可以使用原始递归来写出来:
pm [v,w,x,y,z] = v - (w + (x - (y + z)))
显然lengthEven :: [a] -> Bool
lengthEven = even . length
pm0 [] = 0
pm0 (x:xs) = if lengthEven xs
then x - pm0 xs
else x + pm0 xs
不是compositional - 您需要检查每个位置的整个列表的长度,以确定您是添加还是减去。当折叠函数需要在折叠的每次迭代中遍历整个子树时, Paramorphism 模拟这种类型的原始递归。所以我们至少可以重写代码以符合已建立的模式。
pm0
但这效率低下。 paraL :: (a -> [a] -> b -> b) -> b -> [a] -> b
paraL f z [] = z
paraL f z (x:xs) = f x xs (paraL f z xs)
pm1 = paraL (\x xs acc -> if lengthEven xs then x - acc else x + acc) 0
在每次迭代的遍历中遍历整个列表,从而产生O(n 2 )算法。
我们可以通过注意lengthEven
和lengthEven
可以用para
表示为 catamorphism 来取得进展......
foldr
...这表明我们可以将这两个操作融合到列表中的单个传递中。
cataL = foldr
lengthEven' = cataL (\_ p -> not p) True
paraL' f z = snd . cataL (\x (xs, acc) -> (x:xs, f x xs acc)) ([], z)
我们有一个折叠取决于另一个折叠的结果,我们能够将它们融合到列表中的一个遍历中。 Zygomorphism捕获了这种模式。
pm2 = snd . cataL (\x (isEven, total) -> (not isEven, if isEven
then x - total
else x + total)) (True, 0)
在折叠的每次迭代中,zygoL :: (a -> b -> b) -> -- a folding function
(a -> b -> c -> c) -> -- a folding function which depends on the result of the other fold
b -> c -> -- zeroes for the two folds
[a] -> c
zygoL f g z e = snd . cataL (\x (p, q) -> (f x p, g x p q)) (z, e)
从最后一次迭代中看到它的答案,就像在一个变形中一样,但是f
可以看到两个函数的答案。 g
与g
纠缠在一起。
通过使用第一个折叠函数来计算列表的长度是偶数还是奇数,第二个计算总数,我们将f
写成一个参数。
pm
这是经典的函数式编程风格。我们有一个更高阶的功能来完成消耗列表的繁重工作;我们所要做的就是插入逻辑来汇总结果。结构明显终止(您只需要证明pm3 = zygoL (\_ p -> not p) (\x isEven total -> if isEven
then x - total
else x + total) True 0
的终止),并且它比原始的手写版本更有效。
除了:@AlexR在评论中指出,zygomorphism有一个叫做 mutumorphism 的大姐,它捕获所有的相互递归 它的荣耀。
foldr
在中概括mutu
折叠 允许函数检查前一个的结果 迭代。zygo
您只需忽略额外参数即可从
mutuL :: (a -> b -> c -> b) -> (a -> b -> c -> c) -> b -> c -> [a] -> c mutuL f g z e = snd . cataL (\x (p, q) -> (f x p q, g x p q)) (z, e)
恢复zygo
。mutu
当然,所有这些折叠模式都从列表推广到任意仿函数的固定点:
zygoL f = mutuL (\x p q -> f x p)
将newtype Fix f = Fix { unFix :: f (Fix f) }
cata :: Functor f => (f a -> a) -> Fix f -> a
cata f = f . fmap (cata f) . unFix
para :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
para f = snd . cata (\x -> (Fix $ fmap fst x, f x))
zygo :: Functor f => (f b -> b) -> (f (b, a) -> a) -> Fix f -> a
zygo f g = snd . cata (\x -> (f $ fmap fst x, g x))
mutu :: Functor f => (f (b, a) -> b) -> (f (b, a) -> a) -> Fix f -> a
mutu f g = snd . cata (\x -> (f x, g x))
的定义与zygo
的定义进行比较。另请注意zygoL
,后三折可以zygo Fix = para
实现。在折叠学中,一切都与其他一切有关。
您可以从通用版本恢复列表版本。
cata
答案 1 :(得分:12)
由于还没有其他人回答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
。
(a -> Base [b] (Free (Base [b]) a)) -> a -> [b]
(a -> ListF b (Free (ListF b) a)) -> a -> [b]
Free (ListF b) a)
是一个列表,可能在末尾有一个a
类型的洞。这意味着它与([b], Maybe a)
同构。所以现在我们有:
(a -> ListF b ([b], Maybe a)) -> a -> [b]
消除上一个ListF
,注意到ListF a b
与Maybe (a, b)
同构:
(a -> Maybe (b, ([b], Maybe a))) -> a -> [b]
现在,我非常确定玩类型俄罗斯方块会带来唯一明智的实现:
futuL f x = case f x of
Nothing -> []
Just (y, (ys, mz)) -> y : (ys ++ fz)
where fz = case mz of
Nothing -> []
Just z -> futuL f z
总结生成的函数,futuL
获取一个种子值和一个函数,该函数可能产生至少一个结果,如果产生结果,则可能产生一个新的种子值。
起初我认为这相当于
notFutuL :: (a -> ([b], Maybe a)) -> a -> [b]
notFutuL f x = case f x of
(ys, mx) -> ys ++ case mx of
Nothing -> []
Just x' -> notFutuL f x'
在实践中,也许它或多或少,但一个显着的区别是真正的futu
保证了生产力(即如果f
总是返回,你永远不会被困在等待永远下一个列表元素)。
答案 2 :(得分:11)
Histomorphism 模型动态编程,这是将先前子计算的结果制成表格的技术。 (它有时被称为course-of-value induction。)在组织形态中,折叠函数可以访问折叠的早期迭代结果的表。将其与catamorphism进行比较,其中折叠函数只能看到最后一次迭代的结果。组织形态具有后见之明的好处 - 你可以看到所有的历史。
这是个主意。当我们使用输入列表时,折叠代数将输出b
s的序列。 histo
会在每个b
出现时将其记下来,并将其附在结果表中。历史记录中的项目数等于您已处理的列表图层数 - 当您拆除整个列表时,操作历史记录的长度将等于列表的长度。
这就是迭代列表(ory)的历史:
data History a b = Ancient b | Age a b (History a b)
History
是一系列事物和结果的列表,在结尾处有一个与[]
相对应的额外结果。我们将输入列表的每一层与其相应的结果配对。
cataL = foldr
history :: (a -> History a b -> b) -> b -> [a] -> History a b
history f z = cataL (\x h -> Age x (f x h) h) (Ancient z)
从右到左折叠整个列表后,最终结果将位于堆栈的顶部。
headH :: History a b -> b
headH (Ancient x) = x
headH (Age _ x _) = x
histoL :: (a -> History a b -> b) -> b -> [a] -> b
histoL f z = headH . history f z
(恰好History a
是comonad,但headH
(néeextract
)是我们需要定义的histoL
。)
History
标记输入列表的每一层及其相应的结果。 cofree comonad 捕获了标记任意结构的每一层的模式。
data Cofree f a = Cofree { headC :: a, tailC :: f (Cofree f a) }
(我通过将History
插入ListF
并简化来提出Cofree
。)
将此与免费monad 进行比较,
data Free f a = Free (f (Free f a))
| Return a
Free
是副产品类型; Cofree
是一种产品类型。 Free
层叠了f
s的千层面,在烤宽面条的底部有a
个值。 Cofree
将每层的值a
加上千层面。免费monad是外化标记的树; cofree comonads是通用的内部标记树。
手持Cofree
,我们可以从列表推广到任意仿函数的修复点,
newtype Fix f = Fix { unFix :: f (Fix f) }
cata :: Functor f => (f b -> b) -> Fix f -> b
cata f = f . fmap (cata f) . unFix
histo :: Functor f => (f (Cofree f b) -> b) -> Fix f -> b
histo f = headC . cata (\x -> Cofree (f x) x)
再次恢复列表版本。
data ListF a r = Nil_ | Cons_ a r deriving Functor
type List a = Fix (ListF a)
type History' a b = Cofree (ListF a) b
histoL' :: (a -> History' a b -> b) -> b -> List a -> b
histoL' f z = histo g
where g Nil_ = z
g (Cons_ x h) = f x h
除了:
histo
是futu
的双重身份。看看他们的类型。histo :: Functor f => (f (Cofree f a) -> a) -> (Fix f -> a) futu :: Functor f => (a -> f (Free f a)) -> (a -> Fix f)
futu
为histo
,箭头翻转,Free
替换为。Cofree
cata f . ana g
。组织学看到了过去; futumorphisms预测未来。 很像histo f . futu g
可以融合成 hylomorphism ,Books::chunk(100, function (Collection $entries) { foreach ($entries as $entry) { $entry->slug = 'test'; $entry->save(); } });
可以融合成一个 chronomorphism
即使你跳过了数学部分,this paper by Hinze and Wu也提供了一个关于组织形态及其用法的良好的示例驱动教程。