检查Haskell中的列表是否平坦

时间:2013-04-16 14:25:42

标签: list haskell recursion pattern-matching the-little-schemer

The Little Schemer 中,有一个功能可以检查列表是否是平的:

(define lat?
  (lambda (l)
    (cond
     ((null? l) #t)
     ((atom? (car l)) (lat? (cdr l)))
     (else #f))))

我正在尝试在Haskell中编写相同的递归函数,但没有成功:

is_lat :: [a] -> Bool
is_lat [] = True
is_lat ???

如何检查参数不是[[a]]形式?换句话说,[1,2,3]是有效输入,但[[1,3], [2,4]][[[1,2,3]]]不是。

我想在接受列表的递归函数中进一步使用它,以确保我只处理平面列表。

编辑:我发现由于is_lat :: [a] -> Bool类型签名,人们感到困惑。我现在同意,我不应该在运行时检查类型。但是,是否可以在编译时检查类型?如何才能使该功能仅适用于 flat 列表?或者我应该彻底改变我的思维方式?

4 个答案:

答案 0 :(得分:19)

你不能真正想到嵌套列表在Haskell中与在Scheme中一样,因为它们不是相同的数据结构。 Haskell列表是同质的,其中Lisp“列表”实际上更接近玫瑰树(如下面的C.A.McCann所指出的)。作为一个说明性示例,请查看WYAS48 parsing section如何定义LispVal

如果你真的,真的真的想要进行运行时类型检查,即使它通常是一个坏主意而且在Haskell中非常传统,请查看{{3} }。 Data.Typeable也可能有用。

这个问题的真正答案是“你需要在Haskell中以不同的方式考虑你的论点,而不是在Lisp中,这导致永远不需要在运行时自己执行这个检查”(我说这是一个Common Lisper,所以我了解开始时有多么令人沮丧。)

附录:为了响应您的编辑,Haskell的类型系统会自动确保这一点。例如,如果您的函数类型为foo :: [Int] -> Int,并且您将其传递给["One", "Two", "Three"][[1, 2, 3]],那么您将收到编译时错误,告诉您爆炸的原因以及原因。如果要专门化一个函数,只需声明一个更具体的类型。

例如(不要写这样的代码,它仅用于说明目的),比如你有一个简单的函数,如

myLookup index map = lookup index map 

如果你将它加载到GHCi并运行:t myLookup,它会告诉你函数的类型是myLookup :: Eq a => a -> [(a, b)] -> Maybe b,这意味着它可以获取任何类型的键{{1} (你可以运行Eq的任何东西)。现在,无论出于何种原因,您都要确保使用数字作为键。您可以通过添加更具体的类型声明来确保

==

现在,即使函数体中没有任何东西阻止它处理其他键类型,如果你尝试传递一个myLookup :: Int -> [(Int, a)] -> Maybe a myLookup index map = lookup index map 索引以外的东西,你会在编译时遇到类型错误或Int地图以外的其他内容。结果,这个

[(Int, a)]

将编译并运行正常,但是这个

myLookup :: Int -> [(Int, a)] -> Maybe a
myLookup ix lst = lookup ix lst

main :: IO ()
main = putStrLn . show $ myLookup 1 [(1, "Foo")]

都不会。在我的机器上,它在编译时出错

myLookup :: Int -> [(Int, a)] -> Maybe a
myLookup ix lst = lookup ix lst

main :: IO ()
main = putStrLn . show $ myLookup "Nope.jpg" [("Foo", 1)]

我真的希望这不会让你更进一步。

答案 1 :(得分:13)

对于标准的Haskell列表,这既不可能也不必要,因为Haskell是强类型的;列表的所有元素本身都是列表(在这种情况下,某些[a] = [[b]]的类型为b),或者它们不是。{/ p>

E.g。如果您尝试构建混合列表,您将从编译器中收到错误:

Prelude> ["hello", ["world!"]]

<interactive>:3:12:
    Couldn't match expected type `Char' with actual type `[Char]'
    In the expression: "world!"
    In the expression: ["world!"]
    In the expression: ["hello", ["world!"]]

答案 2 :(得分:11)

函数类型[a] -> Bool隐含地表示forall a. [a] -> Bool,换句话说,它为所有可能元素类型的列表定义相同。这确实包括[[Int]][[[String]]]等类型或您可以想到的任何嵌套深度。但它不能 - 也不能 - 对你的函数来说是什么元素类型。

就此函数而言,输入始终是一个列表,其元素是一些不透明的未知类型。它永远不会收到包含相同opaque类型的嵌套列表。

答案 3 :(得分:0)

嗯,我猜,在Haskell中你只能被http://ideone.com/sPhRCP

main = do   -- your code goes here
print $isFlat [Node 1, Node 2, Node 3]
print $isFlat [Node 1, Node 2, Branch [Node 3, Node 4, Node 5], Node 6]

data Tree a = Node a | Branch [Tree a]

isFlat :: [Tree a] -> Bool
isFlat = all isNode where
                      isNode (Node _) = True
                      isNode _ = False

在非严格类型语言中,每个对象都有运行时类型信息,因此可以是多态的。这可能是一个复杂的强制网络,比如Scala(如果你使用C ++则不那么复杂),或者只是“一切都是对象,对象就是一切”,就像纯动态语言一样(Lisp,JS,...) 。
Haskell是严格类型的。