data BTree a = Empty | Node (BTree a) a (BTree a) -- This is a node-labelled binary tree
有人可以解释下面的Haskell函数吗?
labels :: BTree a -> [a]
labels Empty = []
labels (Node left label right) = labels left ++ [label] ++ labels right
reflect :: BTree a -> BTree a
reflect Empty = Empty
reflect (Node left label right) = Node (reflect left) label (reflect right)
答案 0 :(得分:5)
首先:一些格式化。这就是通常在Haskell中格式化此代码的方式:
data BTree a = Empty
| Node (BTree a) a (BTree a)
labels :: BTree a -> [a]
labels Empty = []
labels (Node left label right) = labels left ++ [label] ++ labels right
reflect :: BTree a -> BTree a
reflect Empty = Empty
reflect (Node left label right) = Node (reflect left) label (reflect right)
(提示:如果您将代码缩进4个空格,它将显示正确的语法突出显示)。 现在让我们来看看它:
data BTree a = Empty
| Node (BTree a) a (BTree a)
定义新的“参数化”数据类型。它被称为参数,因为小a
是一个类型参数。这意味着a
可以替换为任何其他类型,例如Int
,Double
,String
或其他类型。想想C ++中的模板或Java中的泛型。 Empty
和Node
被称为数据类型的构造函数。 BTree a
可以是Empty
或(|
符号表示的)包含Node
,其中包含BTree a
和a
以及另一个BTree a
1}}。定义是递归,因为数据类型(BTree a
)显示在其自己的定义中。
labels :: BTree a -> [a]
labels Empty = []
labels (Node left label right) = labels left ++ [label] ++ labels right
labels
收集树中包含的所有值。第一行是类型声明:它采用带有a
个节点(BTree a
)的二叉树,并将其映射到a
s([a]
)列表。只有这种类型已经让你很好地了解可能发生的事情。
接下来的两行是所谓的模式匹配。这些与其他语言中的case
语句类似:您区分不同的可能性,然后选择适当的情况(尽管它们更强大)。您应该注意它们如何与BTree a
具有的两个构造函数完全对应。如果我们在Empty
节点,那么我们只返回一个空列表([]
)。否则,我们将转到下一行并且Node
必须有BTree a
a
和BTree a
,我们绑定到left
, label
和right
。我们可能已经调用了left
,label
和right
,但这些都是直观的。
现在left
和right
再次属于BTree a
类型,因此我们可以同时调用labels
并期望它们返回a
的列表,即[a]
。因此标签也是递归,因为它在其定义中调用自身。这是一种非常强大的技术,在Haskell中经常使用。 labels
然后连接从labels left
获得的列表,其中只包含当前标签([label]
),然后是labels right
获得的列表。因此,我们可以有效地得出结论,它将左子树中的标签与当前标签和右子树中的标签连接起来,并将其全部放入列表中。
reflect :: BTree a -> BTree a
reflect Empty = Empty
reflect (Node left label right) = Node (reflect left) label (reflect right)
与labels
的工作方式基本相同,只是它返回标签树而不是列表。如此有效,这没有任何作用,它有点昂贵的身份功能。但它是一个更强大的模板。例如,您可以非常轻松地将另一个函数传递给reflect
并将其应用于每个元素。