我习惯了以下Tree
定义:
data Tree a = Empty | Node a (Tree a) (Tree a)
直到我遇到某个地方:
data Tree a = Empty | Leaf a | Node a (Tree a) (Tree a)
让我对Haskell的习语感到好奇。
由于Leaf a
只是Node a Empty Empty
,这个构造函数是否存在?我们也可以使用像
Empty
Tree (Maybe (a, (Tree a), (Tree a)))
或类似的东西。
我写的第二个定义是“扩展最多”的定义,第一个定义是它与最后一个定义的中间位置。什么是实际和理论上最好的?换句话说,性能和数据类型的设计呢?
答案 0 :(得分:7)
如果你想要惯用的Haskell,请使用第一个定义,因为那样你就可以减少与模式匹配的构造函数。
如果你有大量叶子的巨大二叉树,如果你想为每片叶子节省大约16个字节(额外的Tree a
- 指针),请使用第二个定义(在很大程度上取决于哪个平台/编译器)你正在使用多少内存。
您提出的第三种替代方案在技术上是一种有效的表示形式(假设您的意思是Tree (Maybe (a, Tree a, Tree a))
,但使用起来非常繁琐。
答案 1 :(得分:6)
dflemstr的回答很明显,但我想我会添加两个评论(原评论的评论不能容纳)。
首先,通过第二个定义可以节省内存的相同逻辑,可以对此进行类似的论证:
data Tree a = Empty
| Leaf a
| LeftOnly a (Tree a)
| RightOnly a (Tree a)
| Branch a (Tree a) (Tree a)
这是否真正重要取决于您的申请。
第二个也是更重要的一点是,如果您避免直接使用数据构造函数,则可以从这些实现选择中抽象出来。例如,可以为任何这些类型编写等效的foldTree
函数。对于较短的类型,你可以这样做:
data Tree a = Empty | Node a (Tree a) (Tree a)
foldTree :: (a -> b -> b -> b) -> b -> Tree a -> b
foldTree f z Empty = z
foldTree f z (Node v l r) = f v (subfold l) (subfold r)
where subfold = foldTree f z
对于较长的一个,你可以这样写:
data Tree a = Empty | Leaf a | Node a (Tree a) (Tree a)
foldTree :: (a -> b -> b -> b) -> b -> Tree a -> b
foldTree f z Empty = z
foldTree f z (Leaf v) = f v z z
foldTree f z (Node v l r) = f v (subfold l) (subfold r)
where subfold = foldTree f z
对于基于Maybe
的替代方案或我的五构造函数替代方案,也可以这样做。此外,此技术可以应用于您需要的树上的任何其他通用函数。 (事实上,很多这些函数都可以用foldTree
来编写,所以大多数函数都不属于上面的定义。)