不完全确定你在问什么。但是,是的,您输入一个TreeAlgebra
与foldTree
您要在树上执行的计算相对应的值。例如,要对Int
s 树中的所有元素求和,您可以使用以下代数:
sumAlgebra :: TreeAlgebra Int Int
sumAlgebra = TreeAlgebra { leaf = id
, branch = (+) }
这意味着,要获得叶子的总和,对叶子id
中的值应用(什么都不做)。要获得一个分支的总和,请将每个孩子的总和相加。
我们可以说(+)
分支而不是说,这一事实\x y -> sumTree x + sumTree y
是变质的基本属性。它说,要在某个递归数据结构上计算某个函数,只需为其直接子代f
具有 的值即可。f
Haskell 是一种非常独特的语言,因为我们可以抽象地形式化变态的概念。让我们为树中的单个节点创建一个数据类型,对其子节点进行参数化:
data TreeNode a child
= Leaf a
| Branch child child
看看我们在那里做了什么?我们只是用我们选择的类型替换了递归子代。这样我们就可以在折叠时将子树的总和放在那里。
现在来看看真正神奇的东西。我打算用 pseudohaskell 写这个——用真正的 Haskell 写是可能的,但是我们必须添加一些注释来帮助类型检查器,这可能有点令人困惑。我们采用参数化数据类型的“不动点”——即构造一个数据类型T
,使得T = TreeNode a T
. 他们称之为运营商Mu
。
type Mu f = f (Mu f)
仔细看这里。to 的参数Mu
不是类型,例如Int
or Foo -> Bar
。它是一个类型构造函数,例如Maybe
or TreeNode Int
--Mu
自身的参数接受一个参数。(对类型构造函数进行抽象的可能性是使 Haskell 的类型系统在其表达能力上真正脱颖而出的原因之一)。
因此,类型Mu f
被定义为f
使用自身获取并填充其类型参数Mu f
。我将定义一个同义词来减少一些噪音:
type IntNode = TreeNode Int
展开Mu IntNode
,我们得到:
Mu IntNode = IntNode (Mu IntNode)
= Leaf Int | Branch (Mu IntNode) (Mu IntNode)
你看如何Mu IntNode
等同于你的Tree Int
?我们刚刚将递归结构拆开,然后Mu
又将其重新组合在一起。这给了我们一个优势,我们可以一次讨论所有Mu
类型。这为我们提供了定义变态所需要的东西。
让我们定义:
type IntTree = Mu IntNode
我说变态的基本属性是计算某个函数,它的直系子f
级的值就足够了。f
让我们称我们正在尝试计算的事物的类型r
和数据结构node
(IntNode
可能是它的实例化)。因此,要r
在特定节点上进行计算,我们需要将该节点及其子节点替换为它们r
的 s。这个计算有类型node r -> r
。所以变态说如果我们有这些计算之一,那么我们可以计算r
整个递归结构(记住递归在这里明确表示Mu
):
cata :: (node r -> r) -> Mu node -> r
为我们的示例具体化,如下所示:
cata :: (IntNode r -> r) -> IntTree -> r
重申一下,如果我们可以将一个带有r
s 的节点作为它的子节点并计算一个r
,那么我们就可r
以为整个树计算一个 。
为了实际计算这一点,我们需要node
成为一个Functor
——也就是说,我们需要能够将任意函数映射到节点的子节点上。
fmap :: (a -> b) -> node a -> node b
这可以直接为IntNode
.
fmap f (Leaf x) = Leaf x -- has no children, so stays the same
fmap f (Branch l r) = Branch (f l) (f r) -- apply function to each child
现在,最后,我们可以给出一个定义cata
(Functor node
约束只是说node
有一个合适的fmap
):
cata :: (Functor node) => (node r -> r) -> Mu node -> r
cata f t = f (fmap (cata f) t)
我使用参数名称t
作为“树”的助记符值。这是一个抽象的、密集的定义,但实际上非常简单。它说:递归地执行cata f
——我们在树上做的计算——在每个t
孩子(他们自己Mu node
是 s)上得到 a node r
,然后传递那个结果来f
为自己计算结果t
。
回到开头,您定义的代数本质上是定义该node r -> r
函数的一种方式。事实上,给定 a TreeAlgebra
,我们可以很容易地得到 fold 函数:
foldFunction :: TreeAlgebra a r -> (TreeNode a r -> r)
foldFunction alg (Leaf a) = leaf alg a
foldFunction alg (Branch l r) = branch alg l r
因此,树变态可以根据我们的通用定义如下:
type Tree a = Mu (TreeNode a)
treeCata :: TreeAlgebra a r -> (Tree a -> r)
treeCata alg = cata (foldFunction alg)
我没时间了。我知道它很快就变得非常抽象,但我希望它至少能给你一个新的观点来帮助你的学习。祝你好运!