这是提示这篇文章的考试问题:
考虑Haskell中二进制树的以下定义。
data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)
根据此声明,树可以是:Empty
,Leaf
包含类型为a
的元素,或Node
包含类型为{{1}的元素}和两个a
s。
例如,以下表达式的类型为Tree
:
Tree Int
以下函数将Leaf 0
Node (Leaf 1) 2 (Leaf 3)
展平为元素列表(回想一下Tree
是Haskell中列表的串联运算符)。
++
我。为函数flatten Empty = [ ]
flatten (Leaf l) = [l]
flatten (Node t1 r t2) = (flatten t1) ++ (r : (flatten t2))
提供多态类型。证明你的答案。
II。为表达式提供一个类型:
flatten
证明你的答案。
我认为我理解这里的理论,很明显flatten (Node (Leaf 1) 2 (Leaf 3))
为了完成工作,接受多种类型(flatten
,[ ]
和{{1}但是我该怎么解释它的类型呢?除非我假设Leaf
与它接受的类型相同,否则我不确定从何处开始。
关于问题的第二部分,Node
是否有不同的类型,具体取决于它是否以表达式的形式出现?如果是这样的话,我觉得我在最后一段中的假设是正确的,但我希望能够在我身边获得实际逻辑的安慰,而不仅仅是猜测。
提前致谢。
答案 0 :(得分:2)
要正确理解这个问题,您应该了解type inference
和difference between type and data constructor。 Haskell使用类型推断算法,但是考试问题的措辞是因为它的知识不是必需的,直觉就足够了。知道如果在GHCI中键入:t <expression>
,它会为您提供类型,这也很有用。
在此特定示例中,您的定义创建了四个构造函数 - 一元类型构造函数Tree
,nullary数据Empty
,一元数据Leaf
和三元数据Node
。
现在,我们来看看这个功能。函数有三个基于模式匹配的定义,但是我们知道每个定义必须具有相同的类型(或者与其他类型一致)。从第一行开始
flatten Empty = [ ]
我们可以看到flatten需要一些Empty
(我们知道的是Tree
的数据构造函数),并返回一个[]
,什么是列表。但是,Tree
是参数化类型。 Empty
然而不使用参数化,因此它可以属于任何Tree
- 多态类型Tree a
。 []
是一个列表。列表通常是[a]
类型,a
是列表成员的类型。空列表中没有成员,因此列表的类型是多态的 - 保持[a]
。然而,我们说a
来自Tree
参数,并且这行定义中没有任何内容表明列表成员来自树,所以我们可以给它另一个名字,让我们一起去b
。从第一行开始,我们推断出类型为Tree a -> [b]
,或者可以统一的东西。
从第二行开始
flatten (Leaf l) = [l]
我们看到flatten
需要一些Leaf
,参数化为l
。我们知道Leaf l
是Tree a
的数据构造函数,我们知道l
是a
的类型。所以该函数再次需要一些Tree a
。它返回单个元素的列表l
。我们知道l
是a
的类型,因此列表必须是[a]
类型。整个类型是Tree a -> [a]
。
我们可以采用这种类型并将其与第一行的结果统一起来。如果Tree a -> [b]
应与Tree a -> [a]
相同,我们会发现a
必须与b
相同。所以我们统一起来。我们应该看第三行,但在这种情况下,类型不会进一步统一。
还有一件事。如果我们有这样的定义:
flatten Empty = ['e']
flatten (Leaf l) = [l]
flatten (Node t1 r t2) = (flatten t1) ++ (r : (flatten t2))
我们会从第一行看到,结果类型是字符数组[Char]
(或String
,它是别名)。函数类型为Tree a -> [Char]
。在考虑第二行并且看到数组包含叶子包含的内容之后,在统一之后我们必须假设树只包含字符,并且函数的类型将是Tree Char -> [Char]
。
答案 1 :(得分:1)
很明显flatten
为了完成自己的工作,接受多种类型([ ]
,Leaf
和Node
)
我不明白为什么你认为flatten
接受[ ]
。我甚至不确定你的意思; [ ]
不是类型,它是一种类型构造函数,它将类型a
作为参数并返回类型为{{[a]
类型的列表1}}”。
定义a
的三个子句都使它成为一个带有一个参数的函数,因此它有一个flatten
形式的类型,其中x -> y
是参数的类型,{ {1}}是返回类型。在每种情况下,参数都是通过应用x
类型构造函数的构造函数形成的,因此参数的类型为y
。第一个子句中的返回值为Tree
,因此其类型的格式为Tree w
。到目前为止,我们知道最常见的[]
类型的格式为[z]
。第二个子句flatten
提供了一个额外的约束Tree w -> [z]
,因为flatten (Leaf l) = [l]
的类型出现在双方,所以最常见的类型是w = z
形式。
这实际上是l
的有效类型,因此Tree z -> [z]
是最常见的flatten
类型。要查看此内容,您需要完成整个定义并检查它是否对Tree z -> [z]
施加了任何其他约束。
这是一种多态类型:它包含类型变量flatten
。此变量可以通过任何类型实例化。例如,z
使用z
类型为flatten (Leaf (1 :: Int))
,而flatten
使用类型为Tree Int -> [Int]
的{{1}}。
flatten (Leaf True)
是多态的这一事实并不是因为它是一个函数。将它应用于参数并不会改变flatten
的类型,但它可能不会完全使用它。结果表达式的类型仍然可以是多态的。例如,所有Tree Bool -> [Bool]
的{{1}}类型为flatten
,与flatten
相同,因为\x -> flatten x
等同于Tree a -> [a]
,所以不出所料。< / p>
a
的类型来自三个约束:flatten
,其参数类型为\x -> flatten x
,其返回类型为flatten
,flatten (Leaf 3)
接受任何约束键入flatten
并返回Tree a
,[a]
的类型为Leaf
,即a
的类型为Tree a
,前提是此类型为3
类的实例。因此Num a => a
类型3
的约束条件a
是Num
的实例,即flatten (Leaf 3)
具有最常规类型[a]
。