为什么Haskell让我返回一个空列表,其中列表中的列表是预期的?

时间:2013-08-25 18:38:42

标签: haskell typechecking

我是Haskell的新手,并且经历了99 problems translation.这是我对第9号的解决方案:

pack :: (Eq a) => [a] -> [[a]]
pack (xs)
    | null xs = []
    | otherwise = 
        let (matched, unmatched) = span (== head xs) xs
        in [matched] ++ pack unmatched

当类型签名表示函数返回| null xs = []时,我不知道如何允许我[[]]。我已经看到同样问题的其他解决方案做同样的事情。

我的意思是,我不是在抱怨,但这是特别允许的吗?我有什么需要注意的注意事项吗?

我在默认的Windows 7 Haskell Platform 2013.2.0.0安装上使用GHCi,如果有帮助的话。

6 个答案:

答案 0 :(得分:13)

[]是一个空列表。它有以下类型:

[] :: [b] -- Note: I'm using b instead of a because your function already uses a

b可以是一切。你可以选择b [b] ~ [[a]]吗? (〜是类型相等)?是的,只需使用b ~ [a],[]的类型变为:

[] :: [b] :: [[a]]     -- When b ~ [a]

因此[]也是[[a]]类型的值。 []任何类型列表的有效值,无论是列表还是列表列表。

答案 1 :(得分:9)

[Integer]是指“整数值列表”的类型。这种类型的值是否可以实际上不包含任何整数?是的,空列表[]包含零整数。

[[Integer]]是指“整数值列表列表”的类型。这种类型的值是否可以实际包含任何列表?是的,空列表[]包含零列表。

请注意,类型为[]的{​​{1}}与类型相同的[[Integer]]存在很大差异。第一个代表空列表。第二个是非空列表;它只包含一个元素,它本身就是空列表。包含一个空盒子的盒子与一个根本不含任何盒子的盒子不同!我们当然也可以[[]],其中外部非空列表包含多个元素,每个元素都是一个空列表。

如果有帮助,可以将类型[[], [], [], []]视为代表行列表,其中每一行都是整数列表。例如,以下内容:

[[Integer]]

是可视化11, 12, 13; 21, 22, 23, 24; 31; [[Integer]]的一种方式,其中我使用逗号分隔内部列表的元素,并使用分号来终止每一行(也使用换行符来简化阅读)。

在该方案中,[[11, 12, 13], [21, 22, 23, 24], [31]]是由一个空行组成的列表。所以你把它写成一行以分号结尾。而[[]]是一个完全没有行的列表,而不是空行列表。所以你把它写成一个没有分号的空白文件。

如果这有帮助,那么应该很容易看出它如何适用于更为抽象的类型,如[]。通常很难,[[a]]有一些列表类型(无论括号之间写的是什么类型)总是由元素类型的零组成的列表;元素类型本身是否是列表(或其他任何“空”概念)并不重要。

答案 2 :(得分:7)

因为[]在这种情况下属于[[a]]类型:它是一个包含0个字母列表的列表。
通常,空列表可以匹配任何列表类型,因为每个元素(它们全部为零)的类型都是正确的。

答案 3 :(得分:4)

你已经有很多很好的例子,但这可能是一个有用的思考方式。

考虑一个与Haskell列表同构的类型,但没有语法糖:

data List a = Nil
            | Cons a (List a)

Haskell中的每种类型都按其种类进行分类。此类型类型是* -> *类型,您可以将其视为一种类型级函数,它采用基本类型(类型*)并返回另一种基本类型。

您会注意到Cons构造函数也以这种方式参数化;对于传递给a类型的每个不同类型List,它会有所不同。但Nil构造函数以这种方式参数化;这意味着您可以传递给a的每个类型List a的空列表构造函数都相同。即使Nil被约束为单一类型,List a仍然是多态的,因为它的值不依赖于我们选择的a

因此,Nil的类型为List a。这对应于标准列表表示法中的[]。但是[[]]如何转换为这种荒谬的列表类型?值为Cons Nil Nil,类型为List (List a)!在这个语法中,它显然不是一个空列表;它是包含空列表的单元素列表。它仍然是完全多态的,因为还没有任何东西将a限制为单一类型,但它绝对不是空的。

关于您的示例的令人困惑的事情是整个列表类型的名称是相同作为其构造函数之一。如果您在代码中看到[[a]],则必须查看它是在类型上下文还是值上下文中。在前者中,它意味着(在我们的desugared表示法中)List (List a)类型,而在后者中它将意味着值Cons (Cons a Nil) Nil

答案 4 :(得分:3)

这不是答案,但我建议您使用模式匹配而不是headnull吗?

pack :: (Eq a) => [a] -> [[a]]
pack xs = case xs of
    []  -> []
    x:_ ->
        let (matched, unmatched) = span (== x) xs
        in [matched] ++ pack unmatched

这样做有利于编译器静态检查当列表为空时不访问列表的第一个元素。通常,head的使用被认为是非惯用的。

答案 5 :(得分:0)

以下可能是有益的:而不是

pack :: (Eq a) => [a] -> [[a]]
pack (xs)
    | null xs = []
...

尝试这样写:

pack :: (Eq a) => [a] -> [[a]]
pack (xs)
    | null xs = xs -- since xs is the empty list (WRONG!)
...

基于以下(错误)推理:我们知道当参数为空时我们必须返回空列表,因此,我们可以保存键入[](这需要按AltGr,例如在德语键盘上并立即返回参数 - 毕竟, 是空列表,因为确认了空检查。

但是,类型检查器在这一点上不同意。 事实上,对于类型检查器,存在无限多个不同的空列表,每个可能的列表元素类型都有其自己的空列表。但是,在大多数情况下,类型检查器会在您给出[]时识别正确的类型 - 毕竟,在我们的示例中,它知道它必须是该类型的a列表签名。