这个函数发生了什么(haskell)?

时间:2014-01-07 17:12:18

标签: list haskell list-comprehension

我有这个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实际发生了什么?我很难理解......

5 个答案:

答案 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,其中nns中的第一个元素,k[1, 3..]中的第一个元素。所以n = 0k = 1n + k = 1

ns: 0, 1, ?, ...

接下来,下一个元素也是n + k,我们使用ns[1, 3...]的第二个元素。我们现在知道ns的第二个元素是1,所以n = 1k = 3n + 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个奇数,具有适当的起始条件。