你觉得它“令人震惊”,因为你没有预料到它。一旦你习惯了它......好吧,实际上它仍然会绊倒人们。但过了一会儿,你最终还是会想到它。
Haskell 的工作原理是这样的:当你调用一个函数时,什么都没有发生!这个调用被记录在某个地方,仅此而已。这几乎不需要任何时间。你的“结果”实际上只是一个“我欠你的”,告诉计算机要运行什么代码才能得到结果。请注意,不是整个结果;只是它的第一步。对于像整数这样的东西,是只有一步。但是对于列表,每个元素都是一个单独的步骤。
让我给你看一个更简单的例子:
print (take 10 ([1..] ++ [0]))
我与一位 C++ 程序员交谈过,他对这种方法感到“震惊”。当然,“++[0]”部分必须“找到列表的末尾”才能将零附加到它上面?这段代码怎么能在有限时间内完成?!
它看起来像这会构建[1..](在无限列表中),然后++[0] 扫描到此列表的末尾并插入一个零,然后take 10 只修剪掉前 10 个元素,然后打印。当然,这会花费无限的时间。
这就是实际发生的事情。 最外层函数是take,这就是我们开始的地方。 (没想到吧?)take 的定义是这样的:
take 0 ( _) = []
take n ( []) = []
take n (x:xs) = x : (take (n-1) xs)
很明显 10 != 0,所以第一行不适用。所以第二行或第三行都适用。所以现在take 查看[1..] ++ [0] 看它是空列表还是非空列表。
这里最外层的函数是(++)。它的定义看起来类似于
( []) ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
所以我们需要弄清楚哪个方程适用。左边的参数要么是一个空列表(第一行适用),要么不是(第二行适用)。好吧,因为[1..] 是一个无限列表,所以第二行总是 适用。所以[1..] ++ [0] 的“结果”是1 : ([2..] ++ [0])。如您所见,这并没有完全执行;但它执行得足够远,足以说明这是一个非空列表。 take 关心的就是这些。
take 10 ([1..] ++ [0])
take 10 (1 : ([2..] ++ [0]))
1 : take 9 ([2..] ++ [0])
1 : take 9 (2 : ([3..] ++ [0]))
1 : 2 : take 8 ([3..] ++ [0])
1 : 2 : take 8 (3 : ([4..] ++ [0]))
1 : 2 : 3 : take 7 ([4..] ++ [0])
...
1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10 : take 0 ([11..] ++ [0])
1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10 : []
你知道这是如何展开的吗?
现在,回到您的具体问题:(==) 运算符采用一对列表并遍历它们,逐个元素地比较它们以确保它们相等。 一旦发现差异,它立即中止并返回false:
( []) == ( []) = True
(x:xs) == (y:ys) = (x == y) && (xs == ys)
( _) == ( _) = False
如果我们现在尝试,比如说prime 6:
prime 6
factors 6 == [1,6]
??? == [1,6]
1 : ??? == [1,6]
??? == [6]
2 : ??? == [6]
False