我一直在想如何将scanl
推广到任意ADT。 Prelude方法只是将所有内容视为列表(即Foldable
),并将scanl
应用于结构的flatened视图。相反,我倾向于认为scanl
是一个操作,它将一个状态从树的每个节点传递给它的子节点,同时应用一个monoidal操作,因为它从根向下移动到叶子。因此,例如,在Data.Tree
上,我们有:
scan :: (b -> a -> b) -> b -> Tree a -> Tree b
scan fn init (Node element children)
= Node (fn init element)
$ map (treeScan fn (fn init element)) children
所以,例如:
main = do
prettyPrint $ scan (+) 0 $
Node 1 [
Node 1 [
Node 1 [],
Node 1 []],
Node 1 [
Node 1 [],
Node 1 []]]
结果:
1
|
+- 2
| |
| +- 3
| |
| `- 3
|
`- 2
|
+- 3
|
`- 3
与将scanl
独立应用于树的每个路径相同,保留原始结构。
问题很简单:这是一个有意义的概括吗?即,它是常用的,带有明确的解释,也许有不同的名称?
答案 0 :(得分:1)
如果你转移到仿函数的定位点" generic"数据的表示,您可以将扫描(或者更确切地说,它的轻微概括,mapAccum
)视为特殊类型的通用折叠。
这里有一些代码可以勾画出你应该能够继续的模式:
data Fix f a = Roll (f a (Fix f a))
cata :: Functor (f a) => (f a b -> b) -> Fix f a -> b
cata alg (Roll x) = alg $ fmap (cata alg) x
scan :: Functor (f a) => (f a (acc, Fix f b) -> (acc, f b (Fix f b))) -> Fix f a -> Fix f b
scan alg = snd . cata (fmap Roll . alg)
data ListF a b = NilF | ConsF a b deriving Functor
scanAlgList f z NilF = (z, NilF)
scanAlgList f z (ConsF a (acc,b)) =
let val = f a acc
in (val, ConsF val b)
data TreeF a b = LeafF a | BranchF a b b deriving Functor
scanAlgTree f (LeafF x) = (x, LeafF x)
scanAlgTree f (BranchF v (accL,l) (accR,r)) =
let val = f v accL accR
in (val, BranchF v l r)
吉本斯在他的article关于霍纳斯统治的过程中对此进行了讨论。他实际上首先描述了诸如“向下积累”这样的事情。在article from 1992 on"向上和向下累积树木"。