在 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 列表?或者我应该彻底改变我的思维方式?
答案 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是严格类型的。