我很不耐烦,期待理解catamorphism related to this SO question:)
我只练习了Real World Haskell教程的开头。所以,也许我现在要求的方式太多了,如果是这样,请告诉我应该学习的概念。
下面,我引用wikipedia code sample for catamorphism。
我想知道你对下面的foldTree的看法,这是一种遍历树的方法,与其他SO问题和答案相比,还涉及遍历树n-ary tree traversal。 (独立于二元或不二元,我认为下面的catamorphism可以写,以便管理n-ary树)
我评论了我的理解,如果你能纠正我,并且澄清一些事情,我会很高兴。
{-this is a binary tree definition-}
data Tree a = Leaf a
| Branch (Tree a) (Tree a)
{-I dont understand the structure between{}
however it defines two morphisms, leaf and branch
leaf take an a and returns an r, branch takes two r and returns an r-}
data TreeAlgebra a r = TreeAlgebra { leaf :: a -> r
, branch :: r -> r -> r }
{- foldTree is a morphism that takes: a TreeAlgebra for Tree a with result r, a Tree a
and returns an r -}
foldTree :: TreeAlgebra a r -> Tree a -> r
foldTree a@(TreeAlgebra {leaf = f}) (Leaf x ) = f x
foldTree a@(TreeAlgebra {branch = g}) (Branch l r) = g (foldTree a l) (foldTree a r)
在这一点上我遇到了很多困难,我似乎猜测了那个态射叶
将适用于任何Leaf
但是为了将这个代码用于实际,foldTree需要被定义的TreeAlgebra,
一个TreeAlgebra,它有一个定义的态射叶,以便做某事?
但在这种情况下,在foldTree代码中我会期望{f = leaf}而不是相反的
非常欢迎您的任何澄清。
答案 0 :(得分:26)
不完全确定你在问什么。但是,是的,您将TreeAlgebra
提供给与您要在树上执行的计算相对应的foldTree
。例如,要汇总Int
s树中的所有元素,您将使用此代数:
sumAlgebra :: TreeAlgebra Int Int
sumAlgebra = TreeAlgebra { leaf = id
, branch = (+) }
这意味着,要获取叶子的总和,请对叶子中的值应用id
(不执行任何操作)。要获得分支的总和,请将每个子项的总和加在一起。
事实上,我们可以说(+)
代替分支而不是\x y -> sumTree x + sumTree y
,这是catamorphism的基本属性。它表示要在某个递归数据结构上计算某个函数f
,就可以为其直接子项设置f
的值。
Haskell是一种非常独特的语言,因为我们可以抽象地形式化catamorphism的概念。让我们为树中的单个节点创建一个数据类型,并对其子节点进行参数化:
data TreeNode a child
= Leaf a
| Branch child child
看看我们在那里做了什么?我们刚刚用我们选择的类型替换了递归子项。这样我们可以在折叠时将子树的总和放在那里。
现在真的是神奇的事情。我将在pseudohaskell中编写这个 - 在真正的Haskell中编写它是可能的,但是我们必须添加一些注释来帮助typechecker,这可能会让人感到困惑。我们采用参数化数据类型的“固定点” - 即构建数据类型T
,使T = TreeNode a T
。他们将此运算符称为Mu
。
type Mu f = f (Mu f)
仔细看这里。 Mu
的参数不是类型,例如Int
或Foo -> Bar
。它是类型构造函数,如Maybe
或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
类型。这为我们提供了定义catamorphism所需的内容。
让我们来定义:
type IntTree = Mu IntNode
我说catamorphism的基本属性是计算一些函数f
,它的直接子项的值为f
就足够了。让我们调用我们尝试计算的事物的类型r
,数据结构node
(IntNode
将是一个可能的实例化)。因此,要计算特定节点上的r
,我们需要将其子节点替换为r
的节点。此计算的类型为node r -> r
。因此,一个catamorphism说如果我们有其中一个计算,那么我们可以为整个递归结构计算r
(记住递归在这里用Mu
明确表示):< / p>
cata :: (node r -> r) -> Mu node -> r
对于我们的示例,这具体如下:
cata :: (IntNode r -> r) -> IntTree -> r
重申一下,如果我们可以为其子节点选择r
个节点并计算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)上获得{{1}然后将该结果传递给node r
计算f
本身的结果。
将此回到开头,您定义的代数本质上是一种定义t
函数的方法。实际上,给定node r -> r
,我们可以轻松获得折叠函数:
TreeAlgebra
因此,树的变形可以用我们的通用定义如下:
foldFunction :: TreeAlgebra a r -> (TreeNode a r -> r)
foldFunction alg (Leaf a) = leaf alg a
foldFunction alg (Branch l r) = branch alg l r
我没时间了。我知道真的非常抽象,但我希望它至少能给你一个新的观点来帮助你学习。祝你好运!
答案 1 :(得分:4)
我想你是在问一个关于{}的问题。有一个较早的问题,对{}的讨论很好。这些被称为Haskell's record syntax。另一个问题是为什么构造代数。这是一种典型的函数范例,您可以将数据概括为函数。
最着名的例子是Church's construction of the Naturals,其中f = + 1
和z = 0
,
0 = z
,
1 = f z
,
2 = f (f z)
,
3 = f (f (f z))
,
等...
您所看到的实际上与应用于树的想法基本相同。工作教会的例子,树将点击。