在Haskell中展平列表

时间:2013-03-21 23:10:19

标签: list haskell

我试图在Haskell中压缩列表中的任意数量的列表。我知道这个问题之前已经发布在Stack上了,但那里的答案对我来说太复杂了(我是Haskell的新手),或者没有满足我需求的答案(例如,concat赢了工作对我而言,因为我必须亲自为考试学习指南写这个扁平化的功能。我也在Haskell中编写自己的flatten函数,以了解为什么顶级解决方案使用模块。

这是我到目前为止所拥有的。

flatten :: [[a]] -> [a]
flatten [] = []
flatten (x:xs) = flatten x:flatten xs

然而,我收到错误:

Inferred type is not general enough
*** Expression    : flatten
*** Expected type : [[a]] -> [a]
*** Inferred type : [[[a]]] -> [[a]]
编辑:对不起!我误解了我的考试学习问题。列表的所有元素实际上都必须是列表。例如, [[[1,2,3], [4,5,6]], [7,8,9]]而非[1, [2,3,4]]

5 个答案:

答案 0 :(得分:10)

第1步:定义数据。您需要一种支持任意嵌套的数据类型。 [a]没有,所以你无法以这种方式解决问题。

那么数据会是什么样子?一些Lisp符号怎么样:

a
()
(a b c)
((a b) c d)
(a (b c) d (e (f)))

看起来元素可以是原子,也可以是列表。那么让我们说一下Haskell中的上一句话:

data Element t = -- "an element is either ..."
    = Atom t     -- "an atom ..."
    | List [Element t]  -- "or a list of elements"
  deriving (Show)       -- "and please print things for me too, okay?"

第2步:遍历数据。现在您需要编写一个将Element t展平为列表的函数。类型签名会是什么样的?

我建议flatten :: Element t -> [t]。要实现它,它需要两个案例 - 一个用于Atom,一个用于List

flatten :: Element t -> [t]
flatten (Atom x) = ....
flatten (List xs) = .....

请记住,每个等式都必须评估为[t]。祝你好运!

答案 1 :(得分:6)

让我们计算出你的函数类型:如果你有一个列表列表,那就写成[[a]]。您希望将其展平为一个列表[a],因此展平的类型应为[[a]] -> [a]。你也可以欺骗和查找concat的类型,因为你知道你正在重新实现它。

现在让我们来看看实现。第一个案例与[]匹配,并返回[]。这符合[[a]] -> [a]吗?是的,因为参数[]是类型为[something]的空列表,其中某些内容可以包含[a]类型。返回值也符合我们的类型,因为它是类型[whatever]的空列表,其中任何类型都可以有a

现在让我们看看第二种情况,它是否与[[a]] -> [a]类型匹配?与(x:xs)匹配的模式表示x类型为[a]xs类型为[[a]],这很好。问题在于您的递归调用:第一个调用带有flatten x的{​​{1}},其类型为[a]。但我们知道flatten的参数必须是[[a]]类型。

顺便说一下,如果首先声明函数的类型,通常会得到更清晰或更精确的类型错误消息。这是因为一旦发现与您声明的内容存在矛盾,编译器就可以立即停止。如果您忽略签名,则编译器仅检查定义是否与其自身一致。当我宣布类型GHC告诉我:

    Couldn't match type `a' with `[a0]'
      `a' is a rigid type variable bound by
          the type signature for flatten :: [[a]] -> [a] at x.hs:20:1
    Expected type: [[a0]]
      Actual type: [a]
    In the first argument of `flatten', namely `x'
    In the first argument of `(:)', namely `flatten x'

这正是我们自己手动检查类型的原因。我们传递给flatten的参数应该是[[a]]类型,但实际上我们传递的是[a]类型的值。

答案 2 :(得分:3)

好吧,在Haskell中,所有嵌套列表都具有统一的嵌套深度,适用于所有元素。这是类型系统的结果,它要求列表的所有元素具有相同的类型。

以下定义之类的东西永远不会出现问题:

example = [1, [2, 3, 4], [[5, 6], [7]]]

第一个元素是1 :: Integer,第二个元素是[2, 3, 4] :: [Integer],依此类推。

同样的问题适用于您编辑过的示例:

[[[1,2,3], [4,5,6]], [7,8,9]]

此列表的第一个元素的类型为[[Integer]],而第二个元素的类型为[Integer]。但这是不允许的。

第二件事:不仅列表不能具有不均匀的深度,也不存在能够“查看”不同深度的不同嵌套列表的函数。像[[a]] -> [a]这样的类型的函数将“剥离”其输入列表中的一级嵌套,但会将a视为不透明类型 - 它不知道a是什么类型,所以如果你传递[[[Integer]]]参数,flatten无法利用a [Integer]对你提供的那个论点的事实 - a可能是任何东西!

因此,可以在Haskell中完成的唯一列表展平是删除由函数类型固定的某个深度的嵌套。例如,flatten只剥离一层嵌套,使用函数合成我们可以剥离两个或更多:flatten . flatten :: [[[a]]] -> [a]flatten . flatten . flatten :: [[[[a]]]] -> [a],等等。

答案 3 :(得分:2)

[[[1,2,3], [4,5,6]], [7,8,9]]也不是有效的Haskell列表。它包含两个元素;第一个是[[1,2,3], [4,5,6]][[Int]],第二个是[7,8,9],只是[Int]

Haskell中的列表始终为[a]类型,对于某些a,列表的所有元素必须相同。这意味着如果您正在处理嵌套列表,则每个元素必须具有相同的嵌套程度。

基本的Haskell考试似乎不太可能要求你压扁任意级别的嵌套。几乎可以肯定只是希望你实现flatten :: [[a]] -> [a],你可以从类型中分辨出只删除一个“级别”的列表。您可以在类似[[[1], [[2]], [[3]]]的{​​{1}}等列表上调用它,但类型清楚地表明结果将为[[[Int]],即它将返回[[Int]],而不是[[1], [2], [3]]

您尝试执行的操作的问题是,当您的[1, 2, 3]收到类型flatten的参数并将其拆分为[[a]]时(除非它为空),{ {1}}将是(x:xs)列表的元素,因此它将具有类型x。然后,您尝试在其上调用[[a]],以便您可以“一直向下”,但[a]采用类型为flattenflatten的参数为类型为[[a]]

另一种思考为什么这不起作用的方法是,如果它工作,你最终会到达列表的最后一个“级别”,而x会不会甚至可以是列表类型。但是,您会再次将其传递给[a],这会尝试将其与xflatten模式匹配,并且必须失败。您遇到的类型错误是阻止这种情况发生的原因。

答案 4 :(得分:1)

您可以编写一个可以展平任意深层嵌套列表的函数,但嵌套级别必须一致(至少如果您希望保留列表):

{-# LANGUAGE FlexibleInstances
  , OverlappingInstances
  , IncoherentInstances  #-}

class Nested a where
  flatten :: [a] -> [Int]

instance Nested Int where
  flatten = id

instance (Nested a) => Nested [a] where
  flatten xss = concatMap flatten xss

-- e.g. now this works ...
flatten [[1 :: Int,2,3],[2,3,4]]   
-- ... and this too ...
flatten [[[1 :: Int,2,3],[2,3,4]],[[25]]]   
-- ... but this won't work
-- flatten [[[1 :: Int,2,3],[2,3,4]],25]   

如果要允许任意嵌套,则必须包装类型:

{-# LANGUAGE ExistentialQuantification }

class Foo a

instance Foo Int

instance Foo [a]

data F = forall a. Foo a => F a

test = F [F (1 :: Int), F [F (2 :: Int), F [F (3 :: Int), F [F (4 :: Int)]]]] 

现在你可以为这种类型写一个flatten

但是,我不建议作为初学者探索这个。首先,您需要更好地了解实际 应该如何工作的类型。这个东西不是微不足道的,它违背了Haskell类型系统的内容,所以每个解释都会混淆你。尝试以他们想要使用的方式使用这些类型,并在您对常见用例感到满意时回过头来解决这些问题。