第一:一些格式。这就是这段代码在 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
被称为数据类型的构造函数。ABTree a
可以是Empty
或(这就是|
符号)包含 a Node
,其中包含 aBTree a
和a
another BTree a
。该定义是递归的,因为数据类型 ( 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
节点上,那么我们将只返回一个空列表 ( []
)。否则,我们将进入下一行,并且 a Node
which 必须有 aBTree a
和a
aBTree a
我们绑定到left
、label
和right
。我们可能会调用left
,label
以及right
任何我们想要的,但这些都是直观的。
现在left
和right
又是类型BTree a
,所以我们可以调用labels
两者并期望它们返回一个a
s 列表,即[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
并将其应用于其每个元素。