5

我真的是 Haskell 的新手,并且一直在经历99 个问题的翻译。这是我对数字 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。

4

6 回答 6

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]

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

于 2013-08-25T18:48:31.557 回答
9

[Integer]是类型,意思是“整数值列表”。这种类型中是否有一个实际上不包含任何整数的值?是的,空列表[]包含零个整数。

[[Integer]]是类型,意思是“整数值列表的列表”。这种类型中是否有一个实际上不包含任何列表的值?是的,空列表[]包含零列表。

请注意,[]with type与 with the same type[[Integer]]完全不同。[[]]第一个表示列表的空列表。第二个是非空列表;它只包含一个元素,它本身就是一个空列表。一个包含一个空盒子的盒子和一个什么都没有的盒子不是一回事!我们当然也可以有[[], [], [], []],外部非空列表包含几个元素,每个元素都是一个空列表。

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

11, 12, 13;
21, 22, 23, 24;
31;

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

在该方案中,[[]]是由一个空行组成的列表。所以你可以把它写成一个以分号结尾的单行。而[]这是一个根本没有行的列表,甚至没有空行。所以你可以把它写成一个没有分号的空白文件。

如果这有帮助,那么应该很容易看出这如何适用于更抽象的类型,例如[[a]]. 一般来说,[]对于某些列表类型(无论括号之间写的是什么类型),列表总是由元素类型的零组成;元素类型本身是否是一个列表(或任何其他带有“空”概念的东西)都没有关系。

于 2013-08-25T21:29:29.447 回答
7

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

于 2013-08-25T18:40:25.300 回答
4

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

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

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

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

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

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

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

于 2013-08-25T23:20:32.310 回答
3

这不是答案,但我可以建议使用模式匹配而不是headandnull吗?

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

这有利于编译器在列表为空时静态检查您是否访问列表的第一个元素。通常,使用 ofhead被认为是非惯用的。

于 2013-08-25T20:29:05.043 回答
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来自类型签名的 s 列表的列表。

于 2013-08-26T12:26:25.647 回答