如何处理共同递归?

时间:2012-04-21 08:04:12

标签: haskell recursion

好的,基本上我知道选项1或2是否适用于以下情况:

naturals = 0 : map (+ 1) naturals

选项如下:
执行很糟糕,每一步都重新计算:

naturals     = [0]
naturals'    = 0:map (+ 1) [0]          // == [0, 1]
naturals''   = 0:map (+ 1) [0, 1]       // == [0, 1, 2]
naturals'''  = 0:map (+ 1) [0, 1, 2]    // == [0, 1, 2, 3]
naturals'''' = 0:map (+ 1) [0, 1, 2, 3] // == [0, 1, 2, 3, 4]

2。执行不是很糟糕,列表总是无限的,map只应用一次

naturals     = 0:something
                                  |
naturals'    = 0:      map (+ 1) (0:      something)
                                    |
naturals''   = 0:1:    map (+ 1) (0:1:    something')
                                      |
naturals'''  = 0:1:2:  map (+ 1) (0:1:2:  something'')
                                        |
naturals'''' = 0:1:2:3:map (+ 1) (0:1:2:3:something''')

|表示map执行时的位置。

我知道答案可能只是 1 2 ,但我也很欣赏一些关于共同递归的好解释的指示,以清除最后的疑惑:)

3 个答案:

答案 0 :(得分:32)

执行不会像你说的那样“糟糕”。 :)懒惰的评价是你最好的朋友。懒惰是什么意思?

  1. 在真正需要结果之前,不对事情进行评估;
  2. 最多评估一次。
  3. “事物”,这里是“尚未评估的表达”,也称为“thunks”。

    以下是发生的事情:

    您定义

    naturals = 0 : map (+1) naturals
    

    仅仅定义naturals并不需要对其进行评估,因此最初naturals只会指向未评估的表达式0 : map (+1) naturals的thunk:

    naturals = <thunk0>
    

    在某些时候,您的程序可能会在自然模式上进行模式匹配。 (模式匹配本质上是唯一强制在Haskell程序中进行评估的东西。)也就是说,你的程序需要知道自然是空列表还是头元素后跟尾列表。这是评估定义右侧的地方,但只有在需要时才能确定naturals[]是否构建了(:)

    naturals = 0 : <thunk1>
    

    现在,自然会指向构造函数(:)在头元素0上的应用,以及对于仍未评估的尾部的thunk。 (实际上,head元素仍然没有被评估,所以真正的naturals将指向<thunk> : <thunk>形式的某些内容,但我将把这个细节留下来。)

    直到你的程序中的某个稍后点,你可以在尾部模式匹配尾部的thunk被“强制”,即被评估。这意味着要评估表达式map (+1) naturals。评估此表达式会缩短为map上的naturals模式匹配:它需要知道naturals[]是否构建了(:)。 我们看到,此时,naturals已指向(:)的应用程序,而不是指向thunk,因此map的此模​​式匹配不需要进一步评估。 map的应用确实会立即看到naturals足以确定它需要生成(:)本身的应用程序,因此它会:map生成1 : <thunk2> map (+1) <?>其中thunk包含1形式的未评估表达式。 (同样,我们实际上有一个0 + 1的thunk而不是<?>。)naturals指向的是什么?嗯,map的尾部,恰好是naturals = 0 : 1 : <thunk2> 正在产生的东西。因此,我们现在有了

    <thunk2>

    map (+1) (1 : <thunk2>)包含尚未评估的表达式<thunk2>

    在你的程序中稍后,模式匹配可能会强制naturals = 0 : 1 : 2 : <thunk3> ,以便我们得到

    <thunk3>

    map (+1) (2 : <thunk3>)包含尚未评估的表达式{{1}}。等等。

答案 1 :(得分:3)

我花了一段时间来弄清楚这一点,但如果你想找到(比方说)第十亿个自然数,

n = nats !! 1000000000

你在1+操作中遇到了thunk积累。我最后改写了(!!):

nth (x:xs) n = if n==0 then x else x `seq` nth xs (n-1)

我尝试了几种方法来重写nat的定义来强制每个元素,而不是写第n个,但似乎没有任何效果。

答案 2 :(得分:1)

map f xs = f (head xs) : map f (tail xs)
p0 = 0 : map (+ 1) p0

-- when p0 is pattern-matched against:
p0 = "0" :Cons: "map (+ 1) {p0}"    
-- when (tail p0) is pattern-matched against:
-- {tail p0} := p1,
p1 = "(+ 1) (head {p0})" :Cons: "map (+ 1) (tail {p0})"       
-- when (tail p1) is pattern-matched against:
-- {tail p1} := p2,
p2 = "(+ 1) (head {p1})" :Cons: "map (+ 1) (tail {p1})"

Haskell的列表很像Prolog的开放式列表,列表上的共同递归就像尾递归模数一样。一旦你实例化了logvar - 从某个表达式设置了一个列表的单元格值 - 它只是保持该值准备就绪,不再有对原始上下文的引用。

naturals( [A|T] ):- T=[B|R], B=A+1, naturals( T ). % "=" sic! ("thunk build-up")

为了克服Prolog的严格性,我们将来访问推动了这个过程:

naturals( nats(0) ).
next( nats(A), A, nats(B) ):- 
  B is A+1.                    % fix the evaluation to be done immediately
take( 0, Next, Z-Z, Next).
take( N, Next, [A|B]-Z, NZ):- N>0, !, next(Next,A,Next1),
  N1 is N-1,                
  take(N1,Next1,B-Z,NZ).

Haskell毫不费力地处理了它,它的“存储”自然是懒惰的(即列表构造函数是懒惰的,并且列表构造只是通过语言的本质来“唤醒”。)

修复

比较这些:

fix f = f (fix f)         
fix f = x where x = f x   -- "co-recursive" fix ?

现在,当以下使用第一个定义时,您会看到原始问题变为现实:

g = fix $ (0:) . scanl (+) 1

它的经验复杂性实际上是二次的,或更糟。但是对于第二个定义,它应该是线性的。