我试图在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]]
。
答案 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]
采用类型为flatten
且flatten
的参数为类型为[[a]]
。
另一种思考为什么这不起作用的方法是,如果它做工作,你最终会到达列表的最后一个“级别”,而x
会不会甚至可以是列表类型。但是,您会再次将其传递给[a]
,这会尝试将其与x
或flatten
模式匹配,并且必须失败。您遇到的类型错误是阻止这种情况发生的原因。
答案 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类型系统的内容,所以每个解释都会混淆你。尝试以他们想要使用的方式使用这些类型,并在您对常见用例感到满意时回过头来解决这些问题。