我正在尝试在Haskell中创建一个Tree类型。我已经使用这个简单的数据构造函数来存储一个树,其中每个节点可以是空的,是包含整数的叶子,或者是包含一个整数的节点,该整数具有到另外两个叶子/节点的分支。这就是我所拥有的:
module Tree ( Tree(Empty, Leaf, Node) ) where
data Tree = Empty
| Leaf Int
| Node Tree Int Tree
deriving(Eq, Ord, Show, Read)
工作正常,但我需要使Tree类型具有多态性。我试过用'a'代替'Int',但它似乎不起作用。还有另一种系统可以使这些类型具有多态性吗?
答案 0 :(得分:25)
实际上,您可以为树提供类型参数,如 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允许程序员创建多少多态和通用代码(不过,鼓励)。
答案 1 :(得分:16)
data Tree a = Empty
| Leaf a
| Node (Tree a) a (Tree a)
答案 2 :(得分:4)
用a替换Int是正确的开始,但您还需要用Tree a
替换每次出现的Tree(在必要时将其括起来)。 data Tree
部分需要a来声明Tree有一个名为a的类型参数。 Node Tree Int Tree
需要表示子树本身属于Tree a
类型,而不是其他一些树型。
答案 3 :(得分:2)
尝试阅读一下类型构造函数 kind 。
如果您的多态类型取决于某些类型变量,那么您的类型构造函数必须具有反映该类型的类型。
例如,在
中定义的类型构造函数MyBool
data MyBool = False | True
属于*
。也就是说,我的类型构造函数MyBool
不使用参数来定义类型。如果我写下这样的话:
data MyMaybe a = Just a | Nothing
然后类型构造函数MyMaybe
具有类*->*
,也就是说,它需要一个“类型参数”来定义类型。
您可以比较类型构造函数类型如何与数据构造函数类型的工作方式进行比较。
数据构造函数True
可以是类型MyBool
的数据值,没有任何参数。但是数据构造函数Just
是类型a -> MyMaybe a
的值,它将操作而不是类型a的值来创建另一个类型MyMaybe a
的值 - 就像这个ghci会话的例子:
> let x = Just 5
> :t x
Maybe Int
> let y = Just
> :t y
a -> Maybe a
这或多或少与类型构造函数MyMaybe
和MyBool
之间的差异相当。鉴于MyBool
具有种类*
,您可以使用类型MyBool
的值,而无需任何其他类型参数。但是MyMaybe
本身不是一个类型 - 它是一个类型构造函数,它在一个类型上“操作”以创建另一个类型,即它的类型是* -> *
。因此,您不能拥有MyMaybe
类型的内容,而是MyMaybe Int
,MyMaybe Bool
,MyMaybe [Int]
等类型的内容......
如果某个类型是多态的,那么它至少应该是* -> *
种,但它可能是*->*->*
,如:
data MyPair a b = Pair a b
MyPair
需要两个类型参数来定义类型,例如MyPair Int Bool
,MyPair Int Int
等......
take home消息类似于:由于值构造函数具有类型签名,类型构造函数具有类型签名,并且在计划新数据类型时必须考虑这一点。