为什么不是这个F#内函数尾递归?

问题描述:

如果我用一个非常高的初始currentReflection值调用这个函数,我会得到一个堆栈溢出异常,这表明函数不是尾递归的(正确的?)。我的理解是,只要递归调用是该函数的最终计算,那么它应该作为重复使用当前栈帧的尾递归函数进行编译器优化。任何人都知道为什么这里不是这种情况?为什么不是这个F#内函数尾递归?

let rec traceColorAt intersection ray currentReflection = 
     // some useful values to compute at the start 
     let matrix = intersection.sphere.transformation |> transpose |> invert 
     let transNormal = matrix.Transform(intersection.normal) |> norm 
     let hitPoint = intersection.point 

     let ambient = ambientColorAt intersection 
     let specular = specularColorAt intersection hitPoint transNormal 
     let diffuse = diffuseColorAt intersection hitPoint transNormal 
     let primaryColor = ambient + diffuse + specular 

     if currentReflection = 0 then 
      primaryColor 
     else 
      let reflectDir = (ray.direction - 2.0 * norm ((Vector3D.DotProduct(ray.direction, intersection.normal)) * intersection.normal)) 
      let newRay = { origin=intersection.point; direction=reflectDir } 
      let intersections = castRay newRay scene 
      match intersections with 
       | [] -> primaryColor 
       | _ -> 
        let newIntersection = List.minBy(fun x -> x.t) intersections 
        let reflectivity = intersection.sphere.material.reflectivity 
        primaryColor + traceColorAt newIntersection newRay (currentReflection - 1) * reflectivity 

traceColorAt的递归调用显示为较大表达式的一部分。这可以防止尾部呼叫优化,因为traceColorAt返回后需要进一步的计算。

要将此函数转换为尾递归,可以为primaryColor添加一个附加累加器参数。对traceColorAt的最外面的呼叫将通过primaryColor(黑色?)的“零”值,并且每个递归调用将总计其计算的调整,例如,如果你想隐藏呼叫者额外的参数,引入执行大部分工作的一个辅助功能,并呼吁从traceColorAt

let rec traceColorAt intersection ray currentReflection primaryColor 
... 
let newPrimaryColor = primaryColor + ambient + diffuse + specular 
... 
match intersections with 
    | [] -> newPrimaryColor 
    | _ -> 
     ... 
     traceColorAt newIntersection newRay ((currentReflection - 1) * reflectivity) newPrimaryColor 

:代码会看起来像。

+0

完美的伴侣,非常感谢。 – 2011-03-12 07:48:02

+0

函数应用比函数应用具有更高的优先级,因此在原始递归调用之后完成反射率的乘法。所以,这个答案中的代码不会计算出同样的结果。 – RD1 2011-04-06 14:13:28

如果函数仅仅返回另一个函数的结果,则尾递归将起作用。在这种情况下,你有primaryColor + traceColorAt(...),这意味着它不是简单地返回函数的值 - 它也添加了一些东西。

您可以通过传递当前累积颜色作为参数来解决此问题。

+0

没错,固定它。谢谢。 – 2011-03-12 07:47:45