确实,您可以为 Tree 提供类型参数,如 Alexander Poluektov 的示例中所示。够简单!但为什么要停在那里?我们可以享受更多的乐趣。除了具有多态数据的递归结构之外,您还可以使结构在递归本身中具有多态!
首先,抽象出树对自身的引用,就像抽象出对Int 的引用一样,用新参数t 替换递归引用。这给我们留下了这个相当模糊的数据结构:
data TNode t a = Empty
| Leaf a
| Node (t a) a (t a)
deriving (Eq, Ord, Show, Read)
这里已重命名为TNode,因为它不再是真正的树了;只是一个简单的数据类型。现在,为了恢复原始递归并创建一棵树,我们将TNode 扭曲并将其提供给自身:
newtype Tree a = Tree (TNode Tree a) deriving (Eq, Ord, Show, Read)
现在我们可以递归地使用这个Tree,但遗憾的是要付出一些额外的冗长,就像这样:
Tree (Node (Tree Empty) 5 (Tree (Leaf 2)))
那么,除了额外的打字之外,这给了我们什么?简单地说,我们已经将基本树 结构 与其包含的数据以及构造和处理数据的方法分开,从而允许我们编写更多通用函数来处理一个或另一个方面。
例如,我们可以用额外的数据装饰树,或者将额外的东西拼接到树中,而不会影响任何通用的树函数。假设我们想为树的每一部分命名:
newtype NameTree a = NameTree (String, TNode NameTree a) deriving (Eq, Ord, Show, Read)
另一方面,我们可以编写通用的树遍历逻辑:
toList f t = toList' f (f t) []
where toList' f (Node t1 x t2) xs = toList' f (f t1) (x : toList' f (f t2) xs)
toList' f (Leaf x) xs = x:xs
toList' _ Empty xs = xs
给定一个从递归树中提取当前TNode 的函数,我们可以在任何这样的结构上使用它:
treeToList = toList (\(Tree t) -> t)
nameTreeToList = toList (\(NameTree (_, t)) -> t)
当然,这可能远远超出了您的预期,但它是对 Haskell 允许(不,鼓励)程序员创建多少多态性和通用代码的一个很好的体验。