Haskell 为什么子函数的调用次数是指数级的?

Haskell 为什么子函数的调用次数是指数级的?,haskell,recursion,lambda,exponential,Haskell,Recursion,Lambda,Exponential,我对以下Haskell函数有问题: evalPol :: Float -> Float -> Integer -> Integer -> (s -> a -> [(s, [(Float, Float)])]) -> (s -> a) -> [s] -> [((s -> Float), Float)] -&

我对以下Haskell函数有问题:

evalPol :: Float
        -> Float
        -> Integer
        -> Integer
        -> (s -> a -> [(s, [(Float, Float)])])
        -> (s -> a)
        -> [s]
        -> [((s -> Float), Float)]
        -> [((s -> Float), Float)]
evalPol eps gamma maxIter nIter gen pol ss vofss =
  if nIter >= maxIter || delta < eps
    then reverse ((vofs', delta) : vofss)
    else evalPol eps gamma maxIter (nIter + 1) gen pol ss ((vofs', delta) : vofss)
 where delta   = maximum [abs (vofs s - vofs' s) | s <- ss]
       vofs' s = actVal gamma gen vofs s (pol s)
       vofs    = (fst . P.head) vofss
上面的函数条目计数对我来说有意义,如下所示:

  • evalPol
    输入两次:

  • 一次,从外部呼叫时,以及
  • 一次,递归地。(由于:
    maxIter=1
    ,只允许一个递归调用)
  • evalPol.delta
    只被调用一次,因为当第二次(递归地)调用
    evalPol
    时,测试:
    nIter>=maxIter
    成功,并且不需要计算
    delta

  • 我在
    evalPol.vofs'
    中得到441个条目是有意义的,因为我将该函数映射到列表
    ss
    ,该列表中有441个条目

  • 现在,如果我只做一个更改:使用
    maxIter=2
    调用
    evalPol
    ,我发现我的程序没有在合理的时间内终止。 在中断之前让它运行几个小时,我得到以下结果:

    evalPol................2
      evalPol.delta........2
        evalPol.vofs'..60366
    
    evalPol.delta
    的条目数从1改为2(见上文#2)对我来说很有意义,因为我已经设置了
    maxIter=2
    。 然而,我没有料到
    evalPol.vofs'
    中的条目数量会如此激增。 (我希望看到882个条目,每个允许的递归441个。) 在
    maxIter
    中,
    evalPol.vofs'
    的条目数似乎是指数级的。 (我不知道这一点,因为我没有让程序结束。)

    如果我“展开”这个2递归案例,寻找对
    maxIter
    的指数依赖,我会得到:

    -- This is how I call it from outside:
    evalPol eps gamma 2 0 gen pol ss [((const 0), 0)] =                  -- Assume delta >= eps and recurse.
    evalPol eps gamma 2 1 gen pol ss [(vofs', delta), ((const 0), 0)]
    
    -- Now, expand delta
    delta = maximum $ map (abs . uncurry (-) . (vofs &&& vofs')) ss      -- Substitute for vofs, vofs', and pol, using previous iteration's definitions.
          = maximum $ map ( abs
                          . uncurry (-)
                          . ( vofs' &&&
                              \s -> actVal gamma gen vofs' s 0
                            )
                          ) ss
          = maximum $ map ( abs
                          . uncurry (-)
                          . ( \s -> actVal gamma gen (const 0) s 0 &&&
                              \s -> actVal gamma gen (\s' -> actVal gamma gen (const 0) s' 0) s 0
                            )
                          ) ss
    
    正如预期的那样,我看到了递归的发展,但是我没有看到任何嵌套调用到
    evalPol.vofs'
    ,这可能解释了我所观察到的对
    maxIter
    的指数依赖。 此外,我还研究了
    actVal
    函数和
    gen
    函数,它们都没有调用
    evalPol.vofs'

    因此,我无法解释我在
    maxIter=2
    案例中观察到的
    evalPol.vofs'
    中的大量条目。

    我通过使用
    vofs'
    函数的向量表示来解决这个问题。
    这样做消除了以前执行的冗余计算。现在,对于2个递归,我看到了882个对新等价的
    vofs'
    的调用,这正是我所期望的。也就是说,我希望
    evalPol
    的执行时间在
    maxIter
    中是线性的,使用
    vofs'
    的向量表示,这就是我看到的。

    请创建一个。如果没有MCVE,对其进行基准测试和尝试替代方案需要付出太多的努力。是的,将代码缩减到仍然产生此问题的最小示例。否则,“为什么这个代码不起作用?”是投票结束主题时的一个选项;当问题变得如此具体时,问答的普遍适用性会受到严重限制,调试的工作量也会变得很高。我怀疑如果不采用尾部递归的方式,这会大大简化。不过,您应该编辑以包含所有相关细节,然后发布您自己的答案并接受它。就是这样。这些问答项目旨在为整个社区服务,而不仅仅是询问者。:)
    -- This is how I call it from outside:
    evalPol eps gamma 2 0 gen pol ss [((const 0), 0)] =                  -- Assume delta >= eps and recurse.
    evalPol eps gamma 2 1 gen pol ss [(vofs', delta), ((const 0), 0)]
    
    -- Now, expand delta
    delta = maximum $ map (abs . uncurry (-) . (vofs &&& vofs')) ss      -- Substitute for vofs, vofs', and pol, using previous iteration's definitions.
          = maximum $ map ( abs
                          . uncurry (-)
                          . ( vofs' &&&
                              \s -> actVal gamma gen vofs' s 0
                            )
                          ) ss
          = maximum $ map ( abs
                          . uncurry (-)
                          . ( \s -> actVal gamma gen (const 0) s 0 &&&
                              \s -> actVal gamma gen (\s' -> actVal gamma gen (const 0) s' 0) s 0
                            )
                          ) ss