在Haskell中创建多态递归类型
我正在尝试在Haskell中创建一个树类型。我使用了这个简单的数据构造函数来存储一个树,其中每个节点可以是空的,或者是包含整数的叶,或者是包含整数的节点,其分支到其他两个叶/节点。以下是我得到的:在Haskell中创建多态递归类型,haskell,recursion,polymorphism,Haskell,Recursion,Polymorphism,我正在尝试在Haskell中创建一个树类型。我使用了这个简单的数据构造函数来存储一个树,其中每个节点可以是空的,或者是包含整数的叶,或者是包含整数的节点,其分支到其他两个叶/节点。以下是我得到的: module Tree ( Tree(Empty, Leaf, Node) ) where data Tree = Empty | Leaf Int | Node Tree Int Tree deriving(Eq, Ord, Show, Read) 这很好,但我需要使树类型具有多态性。我试着简单
module Tree ( Tree(Empty, Leaf, Node) ) where
data Tree = Empty
| Leaf Int
| Node Tree Int Tree
deriving(Eq, Ord, Show, Read)
这很好,但我需要使树类型具有多态性。我试着简单地用“a”替换“Int”,但似乎不起作用。是否有其他系统可以使这些类型多态?将Int替换为a是正确的开始,但您还需要将每个出现的树替换为
树a
(必要时将其括起来)。数据树
部分需要a来声明树有一个名为a的类型参数。节点树Int-Tree
需要表示子树本身属于树a
的类型,而不是其他树类型。实际上,您可以为树指定一个类型参数,如Alexander Poluektov的示例所示。很简单!但为什么要到此为止?我们可以有更多的乐趣。您可以在递归本身中使结构多态,而不仅仅是具有多态数据的递归结构
data Tree a = Empty
| Leaf a
| Node (Tree a) a (Tree a)
首先,抽象掉树对自身的引用,就像抽象掉对Int
的引用一样,用新参数t
替换递归引用。这就给我们留下了一个相当模糊的数据结构:
data TNode t a = Empty
| Leaf a
| Node (t a) a (t a)
deriving (Eq, Ord, Show, Read)
treeToList = toList (\(Tree t) -> t)
nameTreeToList = toList (\(NameTree (_, t)) -> t)
它在这里被重命名为TNode
,因为它不再是真正的树;只是一个简单的数据类型。现在,为了恢复原始递归并创建一棵树,我们将TNode
旋转并将其馈送到自身:
newtype Tree a = Tree (TNode Tree a) deriving (Eq, Ord, Show, Read)
现在我们可以递归地使用这棵树
,尽管遗憾的是,这是以一些额外的措辞为代价的,比如:
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
的函数,我们可以在任何此类结构上使用该函数:
data TNode t a = Empty
| Leaf a
| Node (t a) a (t a)
deriving (Eq, Ord, Show, Read)
treeToList = toList (\(Tree t) -> t)
nameTreeToList = toList (\(NameTree (_, t)) -> t)
当然,这可能远远超出了您想要做的事情,但这是一个很好的体验,它展示了Haskell允许(不,鼓励)程序员创建了多少多态性和泛型代码。尝试阅读一些关于类型构造函数种类的内容 如果您有一个依赖于某些类型变量的多态类型,那么您的类型构造函数必须有一个反映该类型的类型 例如,在中定义的类型构造函数
MyBool
:
data MyBool = False | True
属于*
类型。也就是说,我的类型构造函数MyBool
不使用参数来定义类型。如果我写下这样的话:
data MyMaybe a = Just a | Nothing
然后类型构造函数MyMaybe
具有种类*->*
,也就是说,它需要一个“类型参数”来定义类型
您可以比较类型构造函数类型的工作方式和数据构造函数类型的工作方式
数据构造函数True
本身可以是MyBool
类型的数据值,不带任何参数。但是数据构造函数Just
是类型为a->mymaybea
的值,它将对类型为a的值进行操作,以创建另一个类型为mymaybea
的值,例如在本ghci会话中:
> let x = Just 5
> :t x
Maybe Int
> let y = Just
> :t y
a -> Maybe a
这与类型构造函数MyMaybe
和MyBool
之间的差异差不多。假定MyBool
具有种类*
,则可以具有类型为MyBool
的值,而无需任何其他类型参数。但是,MyMaybe
本身并不是一个类型——它是一个类型构造函数,对一个类型进行“操作”以创建另一个类型,也就是说,它的类型是*->*
。所以,你不能有MyMaybe
类型的东西,但是mymayboint
,mymaybool
,mymayboit[Int]
类型的东西,等等
如果一个类型是多态的,它至少需要是*->*
,但它可以是*->*->*->*
,如:
data MyPair a b = Pair a b
MyPair
需要两个类型参数来定义类型,如MyPair Int Bool
、MyPair Int
等
带回家的消息类似于:由于值构造函数具有类型签名,类型构造函数具有种类签名,因此在规划新数据类型时必须考虑这一点