我有这个haskell功能,我不太明白。
ns :: [Integer]
ns = 0 : [n+k | (n, k) <- zip ns [1,3..]]
我被要求“花3个小时”。
我认为ns是常量所以它只会用列表的第一个元素压缩,给出(0,1)。然后当添加时给出1的答案。然后它说“需要3 ns”,所以我用列表的前5个元素压缩0,给出...(0,1),(0,3),(0,5 )然后在添加时,我得到[1,3,5]的最终答案。然而,这不是正确的答案。
ns实际发生了什么?我很难理解......
答案 0 :(得分:7)
haskell很懒,所以你可以有递归定义。在这里布局。
ns = 0 : something
(n,k) <- zip (0 : something ) [1,3,5,7...]
(n,k) <- [(0,1) : something )
ns = 0 : 1 : something
(n,k) <- zip ( 0 : 1 : something ) [3,5,7...]
(n,k) <- (0,1) : (1,3) : something
ns = 0 : 1 : 4 : something
(n,k) <- zip ( 0 : 1 : 4 : something ) [5,7...]
(n,k) <- (0,1) : (1,3) : (4,5) : something
ns = 0 : 1 : 4 : 9 : something
....
看看我们如何确定下一个元组是什么,然后添加它的两个元素。这允许我们确定下一个元素。
答案 1 :(得分:1)
Haskell中的所有内容都是惰性的,因此虽然ns
是常量,但这并不意味着列表中的项目不能在以后“添加”(或更准确地说,“计算”)。此外,由于ns
是递归定义的,因此列表中稍后出现的值可能取决于列表中较早出现的值。
让我们一步一步来看看。
首先,我们知道ns
从0开始,所以目前ns
看起来像这样:
ns: 0, ?, ?, ...
那么第一个问号是什么?根据您的函数,它是n + k
,其中n
是ns
中的第一个元素,k
是[1, 3..]
中的第一个元素。所以n = 0
,k = 1
和n + k = 1
。
ns: 0, 1, ?, ...
接下来,下一个元素也是n + k
,我们使用ns
和[1, 3...]
的第二个元素。我们现在知道ns
的第二个元素是1
,所以n = 1
,k = 3
和n + k = 4
。
ns: 0, 1, 4, ...
等等。
答案 2 :(得分:1)
Haskell懒洋洋地评估事物,因此它只会计算所需的值。这意味着我们需要以某种方式需要ns
的值来查看它是如何计算的。
head ns
head (0 : ...)
0
显然,head
对于任何有趣的事情都没有足够的力量,但你已经可以看到ns
的有趣部分被丢弃了。当我们要求更多时,例如打印每个元素,这种影响会更进一步。让我们一个接一个地强制每个元素来看模式。首先,让我们用一个等效函数调用替换列表推导
zipWith f [] _ = []
zipWith f _ [] = []
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
ns = 0 : zipwith (+) ns [1,3..]
现在我们可以逐个评估ns
的元素。实际上,为了更详细,我们正在评估ns
并确定第一个构造函数是(:)
,然后决定将(:)
的第二个参数计算为下一步。我将使用{...}
来表示尚未评估的thunk。
ns
{ 0 } : zipWith (+) ns [1,3...]
{ 0 } : zipWith (+) ({ 0 } : { ... }) [1,3...] -- notice the { 0 } thunk gets evaluated
0 : { 0 + 1 } : zipWith f { ... } [3,5...]
0 : 1 : { 1 + 3 } : zipWith f { ... } [5,7...]
0 : 1 : 4 : { 4 + 5 } : zipWith f { ... } [7,9...]
上面要注意的重要一点是,由于ns
只是逐个评估,因此它永远不需要知道尚未计算的东西。通过这种方式,ns
形成了一个紧凑,聪明的小循环。
答案 3 :(得分:0)
ns :: [Integer]
ns = 0 : [n+k | (n, k) <- zip ns [1,3..]]
这是一个核心数据定义。 ns
是一个常量,一个列表,但它被访问“充实”,因为Haskell是懒惰的。
插图:
1 n1 n2 n3 n4 n5 ... -- the list ns, [n1,n2,n3,...],
2 0 1 4 ... -- starts with 0
3 -----------------
4 1 3 5 7 9 -- [1,3..]
5 -----------------
6 1 4 ... -- sum the lines 2 and 4 pairwise, from left to right, and
7 n2 n3 n4 n5 ... -- store the results starting at (tail ns), i.e. from n2
我们可以准确地看到 访问如何强制列表 ns
逐步存在,例如在print $ take 4 ns
之后,通过命名临时实体:
ns :: [Integer]
ns = 0 : [n+k | (n, k) <- zip ns [1,3..]]
ns = 0 : tail1
tail1 = [n+k | (n, k) <- zip ns [1,3..]]
= [n+k | (n, k) <- zip (0 : tail1) [1,3..]]
= [n+k | (n, k) <- (0,1) : zip tail1 [3,5..]]
= 1 : [n+k | (n, k) <- zip tail1 [3,5..]]
= 1 : tail2
tail2 = [n+k | (n, k) <- zip (1 : tail2) [3,5..]]
= [n+k | (n, k) <- (1,3) : zip tail2 [5,7..]]
= 4 : tail3
tail3 = [n+k | (n, k) <- zip (4 : tail3) [5,7..]]
= 9 : tail4
tail4 = [n+k | (n, k) <- zip (9 : tail4) [7,9..]]
------
ns = 0 : 1 : 4 : 9 : tail4
答案 4 :(得分:0)
这相当于ns = 0 : (zipWith (+) ns [1,3,...])
,这可能更容易理解:第k + 1个元素是第k个元素加上第k个奇数,具有适当的起始条件。